🖥 Swift macOS
source link: https://gavinw.me/swift-macos/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
🖥 Swift Programming for macOS
There are plenty of books, videos, and online resources for developing iOS apps. Despite the fact that iPhone and iPad apps require a Mac for code development, there is little information about actually creating native Mac applications. The examples provided below demonstrate various aspects of Mac app development using the latest versions of Swift and SwiftUI. Hopefully these examples will provide a useful resource for Mac developers. The code repository for this website is located on GitHub at wigging/swift-macos.
Questions, comments, and other feedback can be sent via Email or Twitter. Or submit an Issue on the GitHub repository at wigging/swift-macos. If you would like to support this project, donations can be made to Gavin Wiggins using GitHub Sponsors, Patreon, Buy Me A Coffee, or PayPal. Thank you 😄.
Contents
Getting started
The first step (other than getting a Mac) is to download Xcode from the Mac App Store.
If you’ve been using Xcode for a while, you should remove old simulators that are no longer supported to clear up some space. Use the following terminal command to delete the old simulators:
$ xcrun simctl delete unavailable
AppStorage
The @AppStorage
property wrapper reads and writes values from UserDefaults
. The example below saves a fruit name (a string) to the “fruit” key in UserDefaults
. When the app is relaunched, the saved fruit name will be displayed in the text label. Enter a fruit in the text field then click the “Save fruit” button to save a new fruit to the “fruit” key in UserDefaults
.
import SwiftUI
struct ContentView: View {
@State private var thefruit = ""
@AppStorage("fruit") var fruit = ""
var body: some View {
VStack(spacing: 20) {
TextField("Enter fruit", text: $thefruit)
.multilineTextAlignment(.center)
.frame(maxWidth: 200)
Button("Save fruit") {
fruit = thefruit
}
Text("Saved fruit: \(fruit)")
}
.frame(width: 400, height: 100)
.padding()
}
}
Blur effect
The blur modifier can be used to blur a view.
import SwiftUI
struct ContentView: View {
@State private var blurRadius: CGFloat = 0.0
var body: some View {
VStack(spacing: 40) {
Text("Hello 😁")
.font(.title)
.blur(radius: blurRadius)
Button("My Button"){}
.blur(radius: blurRadius)
Slider(value: $blurRadius, in: 0...20, minimumValueLabel: Text("0"), maximumValueLabel: Text("20")) {
Text("Blur radius")
}
.padding()
}
.frame(width: 400, height: 300)
}
}
Button styles
Several built-in button styles are available for macOS such as the PlainButtonStyle
, LinkButtonStyle
, and BorderlessButtonStyle
. The BorderedButtonStyle
is also the default button style. To create a custom appearance for a button, use the ButtonStyle
protocol. To fully customize the button’s appearance and behavior, use the PrimitiveButtonStyle
protocol.
import SwiftUI
struct RoundedButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(.black)
.padding()
.background(Color.yellow.cornerRadius(12))
.scaleEffect(configuration.isPressed ? 0.95 : 1)
}
}
struct BorderButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? .blue : .red)
.padding(8)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.purple, lineWidth: 3)
)
.onHover { hover in
hover ? NSCursor.pointingHand.push() : NSCursor.pop()
}
}
}
struct DoubleTapButtonStyle: PrimitiveButtonStyle {
@State private var tapped = false
func makeBody(configuration: Configuration) -> some View {
configuration.label
.foregroundColor(tapped ? .yellow : .white)
.padding(10)
.background(Color.red.cornerRadius(8))
.onTapGesture(count: 2, perform: {
tapped.toggle()
configuration.trigger()
})
}
}
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
// Default style
Button("Default Style") {}
// Plain button style
Button("Plain Style") {}.buttonStyle(PlainButtonStyle())
// Link button style
Button("Link Style") {}.buttonStyle(LinkButtonStyle())
// Borderless button style
Button("Borderless Style") {}.buttonStyle(BorderlessButtonStyle())
// Bordered button style
Button("Bordered Style") {}.buttonStyle(BorderedButtonStyle())
// Group button style
VStack {
Button("Button 1") {}
Button("Button 2") {}
}
.buttonStyle(LinkButtonStyle())
// Custom border button style
Button("Custom Style") {}.buttonStyle(BorderButtonStyle())
// Custom rounded button style
Button("Rounded Style") {}.buttonStyle(RoundedButtonStyle())
// Double tap button style
Button("Double Tap Style") {}.buttonStyle(DoubleTapButtonStyle())
}
.frame(width: 400, height: 500)
}
}
Credits
The About window in a Mac application is viewed by selecting the app’s name in the top menu bar, then choosing the About item in the menu. The image below displays Safari’s About window.
To add credits to the About window, add a file named Credits.rtf
to the Xcode project. The text in the RTF file will appear in the About window below the app version and copyright. For example, the following text in Credits.rtf
will appear under the version number in the About window:
My Credits
These are the credits for this app. They go in a rich text file named
"Credits" in the Xcode project. See below for an example of a list.
• First item is here
• Second item here
• Third item goes here
• Fourth items goes here
• Fifth item is here
• Six item is the last
Cursor
The mouse pointer (or cursor) is represented by an arrow on the screen. Different cursors are available in macOS to indicate actions that the user can take with the mouse. See the NSCursor documentation for a list of all the available cursors.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 25) {
Text("Hover over each button to change cursor.")
// i-beam cursor
Button(" i-beam ") {}
.onHover(perform: { hovering in
hovering ? NSCursor.iBeam.push() : NSCursor.pop()
})
// crosshair cursor
Button(" crosshair ") { }
.onHover(perform: { hovering in
hovering ? NSCursor.crosshair.push() : NSCursor.pop()
})
// close-hand cursor
Button(" closed-hand ") { }
.onHover(perform: { hovering in
hovering ? NSCursor.closedHand.push() : NSCursor.pop()
})
// open-hand cursor
Button(" open-hand ") { }
.onHover(perform: { hovering in
hovering ? NSCursor.openHand.push() : NSCursor.pop()
})
// pointing-hand cursor
Button(" pointing-hand ") { }
.onHover(perform: { hovering in
hovering ? NSCursor.pointingHand.push() : NSCursor.pop()
})
}
.frame(width: 400, height: 300)
}
}
Grid lines
A GeometryReader can be used to equally space lines in a view even when that view changes size. This is accomplished by using the width and height of the container view to determine the spacing of the lines.
import SwiftUI
struct ContentView: View {
let xSteps = 5 // purple lines for x-axis grid
let ySteps = 4 // black lines for y-axis grid
var body: some View {
ZStack(alignment: .top) {
GeometryReader { geometry in
Rectangle()
.fill(Color.gray)
// x-axis grid shown as purple lines
ForEach(0..<self.xSteps+1) {
Rectangle()
.fill(Color.purple)
.frame(width: 3)
.offset(x: geometry.size.width / CGFloat(self.xSteps) * CGFloat($0), y: 0.0)
}
// y-axis grid shown as black lines
ForEach(0..<self.ySteps+1) {
Rectangle()
.fill(Color.black)
.frame(height: 3)
.offset(x: 0.0, y: geometry.size.height / CGFloat(self.ySteps) * CGFloat($0))
}
}
}
.frame(minWidth: 400, minHeight: 300)
.padding()
.background(Color.secondary)
}
}
Image
An Image view is used to display an image. For the example below, an image named “homer” is added to the Assets catalog then the image is displayed in the window while preserving its aspect ratio.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image("homer")
.resizable()
.aspectRatio(contentMode: .fit)
}
.padding()
.frame(width: 400, height: 300)
}
}
SF Symbols
Images can also be created using the system name of an SF Symbol. Use the SF Symbols app to look up the names of the available system images.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 20) {
Text("Images as SF Symbols")
Image(systemName: "trash")
Image(systemName: "trash.fill")
Image(systemName: "gear")
.font(.system(size: 32, weight: .bold))
Image(systemName: "gear")
.imageScale(.large)
Image(systemName: "paperplane.circle.fill")
.renderingMode(.original).font(.largeTitle)
}
.frame(width: 400, height: 300)
}
}
System images
Named images specific to macOS can be displayed using the appropriate NSImage.Name
string. See Apple’s documentation for a list of the available system image names.
import SwiftUI
struct ContentView: View {
let prefs = NSImage(named: NSImage.preferencesGeneralName)!
let user = NSImage(named: NSImage.userAccountsName)!
let advanced = NSImage(named: NSImage.advancedName)!
let computer = NSImage(named: NSImage.computerName)!
let folder = NSImage(named: NSImage.folderName)!
let caution = NSImage(named: NSImage.cautionName)!
let group = NSImage(named: NSImage.userGroupName)!
let guest = NSImage(named: NSImage.userGuestName)!
let font = NSImage(named: NSImage.fontPanelName)!
let info = NSImage(named: NSImage.infoName)!
var body: some View {
VStack {
Text("System images available in macOS")
HStack(spacing: 60) {
VStack(spacing: 10) {
Image(nsImage: prefs)
Image(nsImage: user)
Image(nsImage: advanced)
Image(nsImage: computer)
Image(nsImage: folder)
}
VStack(spacing: 10) {
Image(nsImage: caution)
Image(nsImage: group)
Image(nsImage: guest)
Image(nsImage: font)
Image(nsImage: info)
}
}
}
.frame(width: 400, height: 300)
}
}
NSPasteboard
The typical way to copy text is to select it with the mouse then press ⌘C. To do this in code, use the NSPasteboard
class to transfer text to the pasteboard server.
import SwiftUI
struct ContentView: View {
@State private var name = ""
var body: some View {
VStack(spacing: 20) {
Text("Type some text in the text field, then copy it to the clipboard by clicking the Copy Text button.")
TextField("enter some text", text: $name)
.multilineTextAlignment(.center)
Button("Copy Text") {
let pb = NSPasteboard.general
pb.clearContents()
pb.setString(self.name, forType: .string)
}
}
.padding(80)
.frame(width: 400, height: 300)
}
}
Picker control
The picker control selects an item from a set of values. The appearance of the picker can be changed by using different styles and modifiers.
import SwiftUI
struct ContentView: View {
let bands = ["Nirvana", "Pearl Jam", "NIN"]
@State private var selectedBand = 0
@State private var selectedName = 0
var body: some View {
VStack(spacing: 20) {
Picker("Band", selection: $selectedBand) {
ForEach(0..<bands.count) {
Text(self.bands[$0])
}
}
Picker("Band", selection: $selectedBand) {
ForEach(0..<bands.count) {
Text(self.bands[$0])
}
}
.pickerStyle(RadioGroupPickerStyle())
Picker("Band", selection: $selectedBand) {
ForEach(0..<bands.count) {
Text(self.bands[$0])
}
}
.pickerStyle(SegmentedPickerStyle())
Picker("Name", selection: $selectedName) {
Text("Homer Simpson").tag(0)
Text("Lisa Simpson").tag(1)
Text("Bart Simpson").tag(2)
}
.fixedSize()
Picker("Name", selection: $selectedName) {
Text("Homer Simpson").tag(0)
Text("Lisa Simpson").tag(1)
Text("Bart Simpson").tag(2)
}
.labelsHidden()
.fixedSize()
}
.padding()
.frame(width: 400, height: 300)
}
}
ProgressView
A ProgressView
represents completion of a task or the occurance of an activity with an unknown completion time. A default value of 1.0 is used for the total value when tracking completion progress. As the example demonstrates below, different configurations of a progress view are possible.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 30) {
ProgressView()
ProgressView("Loading...")
ProgressView(value: 0.3)
ProgressView(value: 40, total: 100)
ProgressView(value: 0.55) {
Label("Loading", systemImage: "bolt.fill")
}
ProgressView(value: 0.25, label: {
Label("Loading", systemImage: "bolt.fill")
}, currentValueLabel: {
Text("0.25")
})
ProgressView(value: 10.0, total: 100.0, label: {
Label("Download", systemImage: "icloud.and.arrow.down")
}, currentValueLabel: {
HStack {
Text("0")
Spacer()
Text("50")
Spacer()
Text("100")
}
})
}
.padding()
.frame(width: 400, height: 500)
}
}
The next example uses a state variable x
to update the progress view by 10 when the “Add 10” button is clicked. A progress view is often associated with a background task; consequently, the progress view must be updated on the main thread. This is demonstrated with the “Add 20 bg” button in the example shown below.
import SwiftUI
struct ContentView: View {
@State private var x = 0.0
var body: some View {
VStack {
ProgressView(
"Downloading... \(String(format: "%.0f", x))%",
value: x,
total: 100.0
)
// Increase progress bar by 10
Button("Add 10") {
if x < 100.0 {
x += 10.0
}
}
// Increase progress bar by 20 using value from background thread
Button("Add 20 bg") {
DispatchQueue.global(qos: .background).async {
let z = 10.0
DispatchQueue.main.async {
if x < 100.0 {
x += 10.0 + z
}
}
}
}
}
.padding()
.frame(width: 400, height: 300)
}
}
ScrollView
The scroll view displays its content within a scrollable area of the window.
import SwiftUI
struct ContentView: View {
@State private var toggled = true
var body: some View {
ScrollView {
VStack(spacing: 25) {
Text("Scroll up and down").font(.headline)
Toggle(isOn: $toggled) { Text("Toggle 1") }
Toggle(isOn: $toggled) { Text("Toggle 2") }
Toggle(isOn: $toggled) { Text("Toggle 3") }
Button("Button 1") { }
Button("Button 2") { }
Button("Button 3") { }
Text("Last Item in scroll view")
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
.frame(width: 300, height: 200)
}
}
Sidebar navigation
A sidebar can be used to select a destination view in a navigation-based app. This is accomplished by wrapping the views in a NavigationView
and using NavigationLink
to display the different views. In the example below, an AppStorage
property remembers the selected view.
import SwiftUI
struct DetailView: View {
var selected: String?
@AppStorage("selectedStore") private var selectedStore = ""
var body: some View {
switch selected {
case "One":
selectedStore = "One"
return Text("1️⃣ \(selectedStore) View").font(.title).frame(minWidth: 200)
case "Two":
selectedStore = "Two"
return Text("2️⃣ \(selectedStore) View").font(.title).frame(minWidth: 200)
case "Three":
selectedStore = "Three"
return Text("3️⃣ \(selectedStore) View").font(.title).frame(minWidth: 200)
case "Four":
selectedStore = "Four"
return Text("4️⃣ \(selectedStore) View").font(.title).frame(minWidth: 200)
default:
return Text("Default View").font(.title).frame(minWidth: 200)
}
}
}
struct Sidebar: View {
@State private var selection: String? = nil
@AppStorage("selectedStore") private var selectedStore = ""
var body: some View {
List {
NavigationLink(destination: DetailView(selected: selection), tag: "One", selection: $selection) {
Text("One View")
}
NavigationLink(destination: DetailView(selected: selection), tag:"Two", selection: $selection) {
Text("Two View")
}
NavigationLink(destination: DetailView(selected: selection), tag:"Three", selection: $selection) {
Text("Three View")
}
NavigationLink(destination: DetailView(selected: selection), tag:"Four", selection: $selection) {
Text("Four View")
}
}
.onAppear {
selection = selectedStore
}
.listStyle(SidebarListStyle())
.toolbar {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left").help("Toggle Sidebar")
})
}
.frame(minWidth: 150)
}
}
private func toggleSidebar() {
NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
struct ContentView: View {
var body: some View {
NavigationView {
Sidebar()
DetailView()
}
.frame(width: 500, height: 300)
}
}
Sidebar toggle
A sidebar view can be displayed or hidden using the toggleSidebar()
feature from an NSSplitViewController
. At the time of writing this article, SwiftUI does not have this feature but hopefully an upcoming WWDC will offer a SwiftUI solution.
import SwiftUI
struct Sidebar: View {
var body: some View {
List {
Label("Books", systemImage: "book.closed")
Label("Tutorials", systemImage: "list.bullet.rectangle")
Label("Video Tutorials", systemImage: "tv")
Label("Contacts", systemImage: "mail.stack")
Label("Search", systemImage: "magnifyingglass")
}
.listStyle(SidebarListStyle())
.toolbar {
Button(action: toggleSidebar, label: {
Image(systemName: "sidebar.left").help("Toggle Sidebar")
})
}
.frame(minWidth: 150)
}
}
private func toggleSidebar() {
NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
}
struct ContentView: View {
var body: some View {
NavigationView {
Sidebar()
Text("Use button to toggle sidebar.")
.frame(minWidth: 200)
}
.frame(width: 500, height: 300)
}
}
The sidebar view can also be toggled with a keyboard shortcut using Option-Command-S represented by the symbols ⌥⌘S
. This is enabled by adding SidebarCommands()
to the main window group.
import SwiftUI
@main
struct SidebarToggleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
SidebarCommands()
}
}
}
Stepper
The stepper control increments and decrements a value. A closed range can be used to limit the applicable stepper values.
import SwiftUI
struct ContentView: View {
@State private var age = 18
@State private var hours = 4.0
@State private var number = 1
@AppStorage("setting") var setting = 2
var body: some View {
VStack(spacing: 20) {
// Increment or decrement `age` in range of 10-50
Stepper("Age: \(age)", value: $age, in: 10...50)
// Increment or decrement `hours` in range of 1-10 using steps of 0.25
Stepper("Hours: \(hours, specifier: "%g")", value: $hours, in: 1...10, step: 0.25)
// Increment or decrement `number` then print value
Stepper("Number: \(number)", onIncrement: {
print("on increment")
self.number += 1
}, onDecrement: {
print("on decrement")
self.number -= 1
})
// Increment or decrement `number` then print value.
// Also detect when editing begins and ends.
Stepper("Another Number: \(number)", onIncrement: {
print("on increment")
self.number += 1
}, onDecrement: {
print("on decrement")
self.number -= 1
}, onEditingChanged: { edited in
if edited {
print("edited")
} else {
print("not edited")
}
})
// Increment or decrement `setting` and save value to UserDefaults
Stepper("Setting: \(setting)", value: $setting, in: 0...5)
}.frame(width: 400, height: 300)
}
}
A Text view displays one or more lines of read-only text.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
// Basic text view
Text("Hello there")
}
.frame(width: 400, height: 300)
}
Use the font instance method to apply a specific font to an individual Text view.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
Group {
// Large title font
Text("Large Title").font(.largeTitle)
// Title font
Text("Title").font(.title)
// Title 2 font
Text("Title 2").font(.title2)
// Title 3 font
Text("Title 3").font(.title3)
// Headline font
Text("Headline").font(.headline)
// Subheadline font
Text("Subheadline").font(.subheadline)
}
Group {
// Body font
Text("Body").font(.body)
// Callout font
Text("Callout").font(.callout)
// Caption font
Text("Caption").font(.caption)
// Caption 2 font
Text("Caption 2").font(.caption2)
// Footnote font
Text("Footnote").font(.footnote)
}
}.frame(width: 400, height: 400)
}
}
Styles
The Text view in SwiftUI provides several modifiers to customize the appearance and style of the text.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 10) {
Group {
Text("Bold text").bold()
Text("Italic text").italic()
Text("Ultralight text").fontWeight(.ultraLight)
Text("Strikethrough text").strikethrough()
Text("Strikethrough blue").strikethrough(color: .blue)
Text("Underline text").underline()
Text("Underline green").underline(color: .green)
}
Group {
Text("Upper case").textCase(.uppercase)
Text("Lower case").textCase(.lowercase)
Text("Color red text").foregroundColor(.red)
Text("Purple and ").foregroundColor(.purple) + Text("Blue").foregroundColor(.blue)
}
}
.frame(width:400, height: 400)
}
}
Vertical text
Vertical text can be accomplished by rotating a text view 90 degrees. To rotate the frame of the text view, the fixed size modifier must be implemented along with defining the frame size.
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
Text("Vertical text")
.rotationEffect(.degrees(-90))
.fixedSize()
.frame(width: 20, height: 180)
Circle()
.frame(width: 200)
}
.frame(width: 400, height: 300)
}
}
TextField
The TextField structure is a control that provides an editable text field. Various modifiers are available to customize the appearance and text alignment. Actions can be performed when editing begins and ends for the text field or when the return key is pressed.
import SwiftUI
struct ContentView: View {
@State private var text1 = ""
var body: some View {
VStack {
TextField("Example 1", text: $text1)
TextField("Example 2", text: $text1)
.fixedSize()
TextField("Example 3", text: $text1)
.textFieldStyle(RoundedBorderTextFieldStyle())
TextField("Example 4", text: $text1)
.multilineTextAlignment(.center)
TextField("Example 5", text: $text1)
.multilineTextAlignment(.trailing)
TextField("Example 6", text: $text1)
.foregroundColor(.red)
TextField("Example 7", text: $text1, onEditingChanged: { editing in
if editing {
print("is editing")
} else {
print("not editing")
}
})
TextField("Example 8", text: $text1, onCommit: {
print("on commit")
})
}
.padding()
.frame(width: 480, height: 300)
}
}
ViewBuilder
The @ViewBuilder
attribute can be used to build views from closures. To demonstrate, three views are defined as shown below:
struct ViewA: View {
var body: some View {
Text("View A")
.font(.title)
.frame(width: 200, height: 200)
.foregroundColor(.black)
.background(Color.purple)
}
}
struct ViewB: View {
var body: some View {
Text("View B")
.font(.title)
.frame(width: 200, height: 200)
.foregroundColor(.black)
.background(Color.green)
}
}
struct ViewC: View {
var body: some View {
Text("View C")
.font(.title)
.frame(width: 200, height: 200)
.foregroundColor(.black)
.background(Color.orange)
}
}
In the main ContentView
, a function is used to switch between the different views based on the selected picker item. Notice the use of @ViewBuilder
allows the function to provide different child views. Without the @ViewBuilder
attribute, the function would need to wrap each case’s view with AnyView
.
struct ContentView: View {
@State private var selectedView = 1
var body: some View {
VStack {
Picker("Select view:", selection: $selectedView) {
Text("View A").tag(1)
Text("View B").tag(2)
Text("View C").tag(3)
}
.fixedSize()
getView(tag: selectedView)
}
.padding()
.frame(width: 400, height: 300)
}
@ViewBuilder
func getView(tag: Int) -> some View {
switch tag {
case 1:
ViewA()
case 2:
ViewB()
case 3:
ViewC()
default:
ViewA()
}
}
}
WebView
A WKWebView
from the WebKit framework is used to display web content in a window. The web view can be wrapped with NSViewRepresentable
to make it usable with SwiftUI. Content for the web view can be loaded from an HTML file, from a string containing HTML, or from a URL representing a website address. Don’t forget to enable “Outgoing Connections” in the target’s “App Sandbox”; otherwise, the website will not display in the app.
Load an HTML file
<!-- page.html -->
<html>
<head>
<meta charset="utf-8">
<style>
:root { color-scheme: light dark; }
</style>
</head>
<body>
<h1>Hello again friend 😁</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Done.</p>
</body>
</html>
import SwiftUI
import WebKit
struct WebView: NSViewRepresentable {
let htmlFile: String
func makeNSView(context: Context) -> WKWebView {
guard let url = Bundle.main.url(forResource: self.htmlFile, withExtension: "html") else {
return WKWebView()
}
let webview = WKWebView()
webview.loadFileURL(url, allowingReadAccessTo: url)
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) { }
}
import SwiftUI
struct ContentView: View {
var body: some View {
WebView(htmlFile: "page")
.padding()
.frame(width: 480, height: 600)
}
}
Load a string containing HTML
import SwiftUI
import WebKit
struct WebView: NSViewRepresentable {
let content: String
func makeNSView(context: Context) -> WKWebView {
let webview = WKWebView()
webview.loadHTMLString(self.content, baseURL: nil)
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) { }
}
import SwiftUI
let htmlContent = """
<html>
<head>
<style>
:root { color-scheme: light dark; }
</style>
</head>
<body>
<h1>Hello friend!</h1>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Done.</p>
</body>
</html>
"""
struct ContentView: View {
var body: some View {
WebView(content: htmlContent)
.padding()
.frame(width: 480, height: 600)
}
}
Load from a URL
import SwiftUI
import WebKit
struct WebView: NSViewRepresentable {
let url: String
func makeNSView(context: Context) -> WKWebView {
guard let url = URL(string: self.url) else {
return WKWebView()
}
let webview = WKWebView()
let request = URLRequest(url: url)
webview.load(request)
return webview
}
func updateNSView(_ nsView: WKWebView, context: Context) { }
}
import SwiftUI
struct ContentView: View {
var body: some View {
WebView(url: "https://www.apple.com")
.padding()
.frame(width: 480, height: 600)
}
}
Window size
The window size is defined by the frame size of the containing view. In this example the VStack frame is set to a width of 500 and height of 300 which makes the window width 500 and height 300.
import SwiftUI
struct ContentView : View {
var body: some View {
VStack {
Text("Window size is 500 by 300")
}
.frame(width: 500, height: 300)
}
}
Menu items
Menus in Mac apps are typically located at the top of the screen in the menu bar. Menu items can be added to an app’s menu using the commands
modifier on the WindowGroup
.
import SwiftUI
@main
struct MenuItemsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
CommandMenu("My Menu") {
Text("Some text")
Button(action: {}, label: {
Image(systemName: "clock")
Text("Date & Time")
})
Divider()
Button(action: {}, label: {
Text("1️⃣ Item 1")
})
Button(action: {}, label: {
Text("2️⃣ Item 2")
})
Divider()
Menu("Sub Menu") {
Button(action: {}, label: {
Text("Sub Item 1")
})
Button(action: {}, label: {
Text("Sub Item 2")
})
}
}
}
}
}
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK