diff --git a/_data/navigation.yml b/_data/navigation.yml index 68ae2a049..42e5e4ad5 100644 --- a/_data/navigation.yml +++ b/_data/navigation.yml @@ -1,9 +1,3 @@ -- header: Get Started - url: /getting-started/ -# ------------------------------------------------------------------------------ -- header: Blog - url: /blog/ -# ------------------------------------------------------------------------------ - header: Documentation url: /documentation/ # ------------------------------------------------------------------------------ @@ -13,6 +7,9 @@ - header: Tools url: /tools/ # ------------------------------------------------------------------------------ +- header: Blog + url: /blog/ +# ------------------------------------------------------------------------------ - header: Community url: /community/ pages: diff --git a/assets/images/getting-started-guides/swiftui-ios/new-project.png b/assets/images/getting-started-guides/swiftui-ios/new-project.png index fbb3056c5..59f00f9bd 100644 Binary files a/assets/images/getting-started-guides/swiftui-ios/new-project.png and b/assets/images/getting-started-guides/swiftui-ios/new-project.png differ diff --git a/assets/images/getting-started-guides/swiftui-ios/xcode-swift.webp b/assets/images/getting-started-guides/swiftui-ios/xcode-swift.webp new file mode 100644 index 000000000..663c6f411 Binary files /dev/null and b/assets/images/getting-started-guides/swiftui-ios/xcode-swift.webp differ diff --git a/assets/images/landing-page/ai.svg b/assets/images/landing-page/ai.svg new file mode 100644 index 000000000..51618af04 --- /dev/null +++ b/assets/images/landing-page/ai.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/assets/images/landing-page/chip.svg b/assets/images/landing-page/chip.svg new file mode 100644 index 000000000..46b999eb0 --- /dev/null +++ b/assets/images/landing-page/chip.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/assets/images/landing-page/cloud.svg b/assets/images/landing-page/cloud.svg new file mode 100644 index 000000000..5a094e371 --- /dev/null +++ b/assets/images/landing-page/cloud.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/assets/images/landing-page/controller.svg b/assets/images/landing-page/controller.svg new file mode 100644 index 000000000..cd6b087dc --- /dev/null +++ b/assets/images/landing-page/controller.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/assets/images/landing-page/desktop.svg b/assets/images/landing-page/desktop.svg new file mode 100644 index 000000000..b08282665 --- /dev/null +++ b/assets/images/landing-page/desktop.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/assets/images/landing-page/package.svg b/assets/images/landing-page/package.svg new file mode 100644 index 000000000..ab1487a70 --- /dev/null +++ b/assets/images/landing-page/package.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/assets/images/landing-page/phone.svg b/assets/images/landing-page/phone.svg new file mode 100644 index 000000000..85e9dff92 --- /dev/null +++ b/assets/images/landing-page/phone.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/assets/images/landing-page/server.svg b/assets/images/landing-page/server.svg new file mode 100644 index 000000000..bca4437c4 --- /dev/null +++ b/assets/images/landing-page/server.svg @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/assets/images/landing-page/terminal.svg b/assets/images/landing-page/terminal.svg new file mode 100644 index 000000000..ea78640e3 --- /dev/null +++ b/assets/images/landing-page/terminal.svg @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/assets/stylesheets/pages/_landing.scss b/assets/stylesheets/pages/_landing.scss index 4a30963bb..79e3632a8 100644 --- a/assets/stylesheets/pages/_landing.scss +++ b/assets/stylesheets/pages/_landing.scss @@ -1,11 +1,27 @@ .grid-layout-use-cases { @media (min-width: 1000px) { - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(3, 1fr); + } - li:nth-child(2n-1):nth-last-of-type(1) { - grid-column: span 2; - } + .heading { + display: flex; + align-items: center; + gap: 16px; + margin-bottom: 12px; + } + + img { + height: 48px; + width: 48px; + object-fit: contain; + filter: var(--icon-filter); } + + h3 { + margin: 0; + font-size: 1.2em; + } + } .preamble { @@ -147,4 +163,4 @@ font-weight: bold; border-radius: 4px; } -} +} \ No newline at end of file diff --git a/getting-started/cli-swiftpm/index.md b/getting-started/cli-swiftpm/index.md index b373076f0..061f7b7b4 100644 --- a/getting-started/cli-swiftpm/index.md +++ b/getting-started/cli-swiftpm/index.md @@ -3,19 +3,16 @@ layout: page title: Build a Command-line Tool --- -> The source code for this guide can be found [on GitHub](https://github.com/apple/swift-getting-started-cli) - {% include getting-started/_installing.md %} ## Bootstrapping -Let’s write a small application with our new Swift development environment. -To start, we’ll use SwiftPM to make a new project for us. In your terminal of choice run: +Let’s write a small application with our new Swift development environment. To start, we’ll create a new project which compiles to an executable. In your terminal of choice, run: ~~~bash -$ mkdir MyCLI -$ cd MyCLI -$ swift package init --name MyCLI --type executable +mkdir MyCLI +cd MyCLI +swift package init --name MyCLI --type executable ~~~ This will generate a new directory called MyCLI with the following files: @@ -31,30 +28,30 @@ This will generate a new directory called MyCLI with the following files: `Sources/main.swift` is the application entry point and where we’ll write our application code. -In fact, SwiftPM generated a "Hello, world!" project for us! +In fact, Swift generated a "Hello, world!" project for us! We can run the program by running `swift run` in our terminal. ~~~bash $ swift run MyCLI Building for debugging... -[3/3] Linking MyCLI -Build complete! (0.68s) +[8/8] Applying MyCLI +Build of product 'MyCLI' complete! (0.71s) Hello, world! ~~~ ## Adding dependencies -Swift based applications are usually composed from libraries that provide useful functionality. +Swift-based applications are usually composed from libraries that provide useful functionality. In this project, we’ll use a package called [example-package-figlet](https://github.com/apple/example-package-figlet) which will help us make ASCII art. -You can find more interesting libraries on [Swift Package Index](https://swiftpackageindex.com) -- the unofficial package index for Swift. +You can find more interesting libraries at the [Swift Package Index](https://swiftpackageindex.com). To do so, we extend our `Package.swift` file with the following information: ~~~swift -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -62,7 +59,7 @@ import PackageDescription let package = Package( name: "MyCLI", dependencies: [ - .package(url: "https://github.com/apple/example-package-figlet", branch: "main"), + .package(url: "https://github.com/apple/example-package-figlet", branch: "main"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -70,14 +67,14 @@ let package = Package( .executableTarget( name: "MyCLI", dependencies: [ - .product(name: "Figlet", package: "example-package-figlet"), - ], - path: "Sources"), + .product(name: "Figlet", package: "example-package-figlet") + ] + ) ] ) ~~~ -Running `swift build` will instruct SwiftPM to download the new dependencies and then proceed to build the code. +Running `swift build` will instruct Swift to download the new dependencies and then proceed to build the code. Running this command also created a new file for us, `Package.resolved`. This file is a snapshot of the exact versions of the dependencies we are using locally. @@ -122,7 +119,7 @@ To add this capability to our application, we add a dependency on [swift-argumen To do so, we extend our `Package.swift` file with the following information: ~~~swift -// swift-tools-version: 5.8 +// swift-tools-version: 6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -130,8 +127,8 @@ import PackageDescription let package = Package( name: "MyCLI", dependencies: [ - .package(url: "https://github.com/apple/example-package-figlet", branch: "main"), - .package(url: "https://github.com/apple/swift-argument-parser", from: "1.0.0"), + .package(url: "https://github.com/apple/example-package-figlet", branch: "main"), + .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -141,8 +138,8 @@ let package = Package( dependencies: [ .product(name: "Figlet", package: "example-package-figlet"), .product(name: "ArgumentParser", package: "swift-argument-parser"), - ], - path: "Sources"), + ] + ) ] ) ~~~ @@ -150,23 +147,25 @@ let package = Package( We can now import the argument parsing module provided by `swift-argument-parser` and use it in our application: ~~~swift -import Figlet import ArgumentParser +import Figlet @main struct FigletTool: ParsableCommand { - @Option(help: "Specify the input") - public var input: String + @Option(help: "Specify the input") + public var input: String - public func run() throws { - Figlet.say(self.input) - } + public func run() { + Figlet.say(input) + } } ~~~ For more information about how [swift-argument-parser](https://github.com/apple/swift-argument-parser) parses command line options, see [swift-argument-parser documentation](https://github.com/apple/swift-argument-parser) documentation. -Once we save that, we can run our application with `swift run MyCLI --input 'Hello, world!'` +Once we save that, we can run our application with `swift run MyCLI --input +'Hello, world!'` (macOS / Linux) or `swift run MyCLI --input "Hello, world!"` +(Windows) Note we need to specify the executable in this case, so we can pass the `input` argument to it. diff --git a/getting-started/swiftui/index.md b/getting-started/swiftui/index.md index ba7c9bda2..6fb123d5f 100644 --- a/getting-started/swiftui/index.md +++ b/getting-started/swiftui/index.md @@ -1,413 +1,63 @@ --- layout: page -title: Build an iOS app with SwiftUI +title: Build Mobile Apps with Swift --- -> The source code for this guide can be found [on GitHub](https://github.com/0xTim/swift-org-swiftui-tutorial) +Swift is the primary language for building polished, unique apps for iOS, +watchOS, visionOS, and other Apple platforms. With built-in support for app +development in Xcode, as well as powerful libraries like SwiftUI and SwiftData, +Swift is the perfect choice for your next mobile app project. -In this tutorial you’re going to use Swift and SwiftUI to build a small app to recommend fun new activities to users. Along the way you’ll meet several of the basic components of a SwiftUI app, including text, images, buttons, shapes, stacks, and program state. +![Swift in Xcode]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/xcode-swift.webp) -To get started, you’ll need to [download Xcode from the Mac App Store](https://apps.apple.com/app/xcode/id497799835?mt=12). It’s free, and comes with Swift and all the other tools you need to follow this tutorial. +## Getting started -Go ahead and launch Xcode once it’s installed, then select Create a new Xcode Project. Select the iOS tab at the top, then select the App template and press Next. +When building mobile apps with Swift, you'll want a full development +environment, rather than just the Swift compiler. Xcode provides the latest +version of Swift, along with tools for designing and previewing your user +interface, simulators for testing your app, and tools for uploading it to the +App Store. -**Tip:** Although we’ll be targeting iOS 16, our code will also work great on macOS Ventura and beyond. +Xcode is designed for running on Mac, and you can download it for free from [the +Mac App Store](https://apps.apple.com/us/app/xcode/id497799835). -When making a new project, Xcode will ask you for a few pieces of information: +## Your first app with Swift -- For Product Name, enter “WhyNotTry”. -- For Organization Identifier you can enter com.example. In real apps we’d normally enter our own domain name here, e.g. org.swift. -- For Interface make sure SwiftUI is selected. -- You can uncheck the boxes for Core Data and Include Tests; we won’t be using them here. +There are many great tutorials for learning to develop mobile apps with Swift. +If you're starting a new app, you'll probably want to use SwiftUI, a modern way +to build beautiful user interfaces using declarative Swift code. -![New Xcode Project]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/new-project.png) +If you're already familiar with UI development from other platforms, you may +find the [Introducing SwiftUI](https://developer.apple.com/tutorials/swiftui/) +tutorial a great place to start. This tutorial teaches the fundamentals of Swift +and SwiftUI through development of a sophisticated app with animation, storage +and graphics, and is a good starting point for further learning. -When you press Next, Xcode will ask where you want to save the project. You’re welcome to choose wherever suits you, but you might find your Desktop is easiest. Once that’s done, Xcode will create the new project for you, then open ContentView.swift for editing. This is where we’ll write all our code, and you’ll see some default SwiftUI code in there for us. +Another great learning resource is Hacking with Swift, a free learning resource +that includes [100 Days of +SwiftUI](https://www.hackingwithswift.com/100/swiftui) -- a comprehensive +tutorial to learning Swift development. -![Initial SwiftUI project]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/initial-view.png) +Finally, this video provides an overview of the philosophy of SwiftUI and how it +takes advantage of Swift to provide a modern, high-performance framework for +constructing UI: -The example code Xcode made for us creates a new view called `ContentView`. Views are how SwiftUI represents our app’s user interface on the screen, and we can add custom layout and logic in there. + -On the right-hand side of Xcode, you’ll see a live preview of that code running – if you make a change to the code on the left, it will appear in the preview straight away. If you can't see the preview, follow [these instructions](https://developer.apple.com/documentation/swiftui/previews-in-xcode) to enable it. +## Useful packages and libraries -For example, try replacing the default `body` code with this: +The Swift ecosystem includes a wide variety of packages that are useful for +mobile apps, including: -```swift -var body: some View { - Text("Hello, SwiftUI!") -} -``` +- [SwiftData](https://developer.apple.com/documentation/swiftdata/), which + enables you to add data persistence to your app easily. +- [Swift Charts](https://developer.apple.com/documentation/charts), a powerful + package for transforming data into beautiful visualizations. +- [MapKit](https://developer.apple.com/documentation/mapkit/), which brings + powerful mapping and satellite imagery into your app. -![Hello SwiftUI]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/hello-swift-ui.png) +## What about other platforms? -You should see your preview update immediately, which makes for really fast prototyping while you work. This is a computed property called `body`, and SwiftUI will call that whenever it wants to display our user interface. - - -## Building a static UI - -In this app we’re going to show the user a new activity they could try to keep fit, such as basketball, golf, and hiking. To make it a little more attractive, we’ll display each activity using its name, and also an icon representing the activity, then add a splash of color behind it. - -The main part of our user interface will be a circle showing the currently recommended activity. We can draw circles just by writing `Circle`, so replace the `Text("Hello, SwiftUI!")` view with this: - -```swift -Circle() -``` - -![SwiftUI Circle]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle.png) - -In your preview you’ll see a large black circle fills the available screen width. That’s a start, but it’s not quite right – we want some color in there, and ideally adding a little space on either side so it doesn’t look so tight. - -Both of these can be accomplished by calling methods on the `Circle` view. We call these *view modifiers* in SwiftUI because they modify the way the circle looks or works, and in this case we need to use the `fill()` modifier to color the circle, then the `padding()` modifier to add some space around it, like this: - -```swift -Circle() - .fill(.blue) - .padding() -``` - -![SwiftUI Circle with Color and Padding]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-color.png) - -The `.blue` color is one of several built-in options, such as `.red`, `.white`, and `.green`. These are all appearance aware, which means they look subtly different depending on the whether the device is in dark mode or light mode. - -Over that blue circle we’re going to place an icon showing the activity we recommend. iOS comes with several thousand free icons called *SF Symbols*, and there’s a [free app you can download that shows you all the options](https://developer.apple.com/sf-symbols/). Each of these icons is available in multiple weights, can be scaled up or down smoothly, and many can also be colored. - -Here, though, we want something nice and simple: we want just one icon placed over our circle. This means using another modifier called `overlay()`, which places one view over another. Modify your code to this: - -```swift -Circle() - .fill(.blue) - .padding() - .overlay( - Image(systemName: "figure.archery") - ) -``` - -![SwiftUI Circle with Icon]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-icon.png) - -You should see a small, black archery icon over our large, blue circle – it’s the right idea, but it doesn’t look great. - -What we really want is the archery icon to be much bigger, and also much more visible on that background. For that we need another two modifiers: `font()` to control the size of the icon, and `foregroundColor()` to change its color. Yes, we use a font modifier to control the icon’s size – SF Symbols like this one automatically scale with the rest of our text, which makes them really flexible. - -Adjust your `Image` code to this: - -```swift -Image(systemName: "figure.archery") - .font(.system(size: 144)) - .foregroundColor(.white) -``` - -![SwiftUI Circle with Icon Sized]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-icon-sized.png) - -**Tip:** That `font()` modifier asks for a 144-point system font, which is nice and big on all devices. - -That should now look a lot better. - -Next, let’s add some text below the image so it’s clear to the user what the suggestion is. You already met the `Text` view and the `font()` modifier, so you can add this code below the `Circle` code: - -```swift -Text("Archery!") - .font(.title) -``` - -Rather than using a fixed font size, that uses one of SwiftUI’s built in Dynamic Type sizes called `.title`. This means the font will grow or shrink depending on the user’s settings, which is usually a good idea. - -If everything has gone to plan, your code should look like this: - -```swift -var body: some View { - Circle() - .fill(.blue) - .padding() - .overlay( - Image(systemName: "figure.archery") - .font(.system(size: 144)) - .foregroundColor(.white) - ) - - Text("Archery!") - .font(.title) -} -``` - -![Circle With Title Text]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/circle-with-title.png) - -However, what you see in Xcode’s preview probably won’t match what you were expecting: you’ll see the same icon as before, but no text. What gives? - -The problem here is that we’ve told SwiftUI our user interface will have two views inside – the circle and some text – but we haven’t told it how to arrange them. Do we want them side by side? One above the other? Or in some other kind of layout? - -We get to choose, but I think here a vertical layout will look better. In SwiftUI we get that with a new view type called `VStack`, which is placed *around* our current code, like this: - -```swift -VStack { - Circle() - .fill(.blue) - .padding() - .overlay( - Image(systemName: "figure.archery") - .font(.system(size: 144)) - .foregroundColor(.white) - ) - - Text("Archery!") - .font(.title) -} -``` - -And now you should see the layout you expected earlier: our archery icon above the text “Archery!”. - -![Circle With Title Text in a VStack]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/circle-with-title-vstack.png) - -That’s much better! - -To finish up our first pass at this user interface, we can add a title at the top. We already have a `VStack` that allows us to position views one above the other, but I don’t want the title inside there too because later on we’ll be adding some animation for that part of our screen. - -Fortunately, SwiftUI lets us nest stacks freely, meaning that we can place a `VStack` inside another `VStack` to get the exact behavior we want. So, change your code to this: - -```swift -VStack { - Text("Why not try…") - .font(.largeTitle.bold()) - - VStack { - Circle() - .fill(.blue) - .padding() - .overlay( - Image(systemName: "figure.archery") - .font(.system(size: 144)) - .foregroundColor(.white) - ) - - Text("Archery!") - .font(.title) - } -} -``` - -That makes the new text have a large title font, and also makes it bold so it stands out better as a real title for our screen. - -![Why Not Try Title Added]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/why-not-try-title.png) - -Now we have two `VStack` views: an inner one that holds the circle and “Archery!” text, and an outer one that adds a title around the inner `VStack`. This will be very helpful later on when we add animation! - - -## Bringing it to life - -As much fun as archery is, this app really needs to suggest a random activity to users rather than always showing the same thing. That means adding two new properties to our view: one to store the array of possible activities, and one to show whichever one is currently being recommended. - -SF Symbols has lots of interesting activities to choose from, so I’ve picked out a handful that work well here. Our `ContentView` struct already has a `body` property containing our SwiftUI code, but we want to add new properties outside that. So, change your code to this: - -```swift -struct ContentView: View { - var activities = ["Archery", "Baseball", "Basketball", "Bowling", "Boxing", "Cricket", "Curling", "Fencing", "Golf", "Hiking", "Lacrosse", "Rugby", "Squash"] - - var selected = "Archery" - - var body: some View { - // ... - } -} -``` - -**Important:** Notice how the `activities` and `selected` properties are *inside* the struct – that means they belong to `ContentView`, rather than just being free-floating variables in our program. - -That creates an array of various activity names, and selects archery as the default. Now we can use the selected activity in our UI using string interpolation – we can place the `selected` variable directly inside strings. - -For the activity name this is straightforward: - -```swift -Text("\(selected)!") - .font(.title) -``` - -For the image this is a little more complicated, because we need to prefix it with `figure.` then lowercase the activity name – we want `figure.archery` rather than `figure.Archery`, otherwise the SF Symbol won’t be loaded. - -So, change your `Image` code this: - -```swift -Image(systemName: "figure.\(selected.lowercased())") -``` - -Those changes mean our UI will display whatever the `selected` property is set to, so can see it all change if you place a new string in that property: - -```swift -var selected = "Baseball" -``` - -![Showing Baseball]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/baseball.png) - -Of course, we want that to change *dynamically* rather than having to edit the code each time, so we’re going to add a button below our inner `VStack` that will change the selected activity every time it’s pressed. This is still inside the outer `VStack`, though, which means it will be arranged below the title and activity icon. - -Add this code now: - -```swift -Button("Try again") { - // change activity -} -.buttonStyle(.borderedProminent) -``` - -![Try Again Button]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/try-again-button.png) - -So, your structure should be this: - -```swift -VStack { - // "Why not try…" text - - // Inner VStack with icon and activity name - - // New button code -} -``` - -The new button code does three things: - -1. We create the `Button` by passing in a title to show as the button’s label. -2. The `// change activity` comment is code that will be run when the button is pressed. -3. The `buttonStyle()` modifier tells SwiftUI we want this button to stand out, so you’ll see it appear in a blue rectangle with white text. - -Just having a comment as the button’s action isn’t very interesting – really we want to make it set `selected` to a random element from the `activities` array. We can pick a random element from the array by calling the helpfully named `randomElement()` method on it, so replace the comment with this: - -```swift -selected = activities.randomElement() -``` - -That code *looks* right, but it will actually cause compiler errors. We’re telling Swift to pick a random element from the array and place it into the `selected` property, but there’s no way for Swift to be sure there’s anything in that array – it could be empty, in which case there’s no random element to return. - -![Random Element Error]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/random-element-error.png) - -Swift calls these *optionals*: `randomElement()` won’t return a regular string, it will return an *optional* string. This means the string might not be there, so it’s not safe to assign to the `selected` property. - -Even though we know the array will never be empty – it will *always* have activities in there – we can give Swift a sensible default value to use just in case the array happens to be empty in the future, like this: - -```swift -selected = activities.randomElement() ?? "Archery" -``` - -That partly fixes our code, but Xcode will still be showing an error. The problem now is that SwiftUI doesn’t like us changing our program’s state right inside our view structs without warning – it wants us to mark all the mutable state ahead of time, so it knows to watch for changes. - -![Non-@State mutating]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/non-state-mutating.png) - -This is done by writing `@State` before any view properties that will change, like this: - -```swift -@State var selected = "Baseball" -``` - -This is called a *property wrapper*, meaning that it wraps our `selected` property with some extra logic. The `@State` property wrapper allows us to change view state freely, but it also automatically watches its property for changes so that it can make sure the user interface stays up to date with the latest values. - -That fixes the two errors in our code, so you can now press Cmd+R to build and run your app in the iOS simulator. It will suggest baseball by default, but every time you press “Try again” you’ll see it change. - -Running The App in the Simulator - -## Adding some polish - -Before we’re done with this project, let’s add a handful more tweaks to make it better. - -First, an easy one: Apple recommends that local view state always be marked with `private` access control. In larger projects, this means you can’t accidentally write code that reads one view’s local state from another, which helps keep your code easier to understand. - -This means modifying the `selected` property like so: - -```swift -@State private var selected = "Baseball" -``` - -Second, rather than always showing a blue background, we can pick a random color each time. This takes two steps, starting with a new property of all the colors we want to select from – put this next to the `activities` property: - -```swift -var colors: [Color] = [.blue, .cyan, .gray, .green, .indigo, .mint, .orange, .pink, .purple, .red] -``` - -Now we can change our circle’s `fill()` modifier to use `randomElement()` on that array, or `.blue` if somehow the array ends up being empty: - -```swift -Circle() - .fill(colors.randomElement() ?? .blue) -``` - -Third, we can separate the activity `VStack` and “Try again” button by adding a new SwiftUI view between them, called `Spacer`. This is a flexible space that automatically expands, which means it will push our activity icon to the top of the screen, and the button to the bottom. - -Insert it between the two, like this: - -```swift -VStack { - // current Circle/Text code -} - -Spacer() - -Button("Try again") { - // ... -} -``` - -If you add multiple spacers, they will divide the space equally between them. If you try placing a second spacer before the “Why not try…” text you’ll see what I mean – SwiftUI will create and equal amount of space above the text and below the activity name. - -![View With Spacers]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/spacers.png) - -And fourth, it would be nice if the change between activities was smoother, which we can do by animating the change. In SwiftUI, this is done by wrapping changes we want to animate with a call to the `withAnimation()` function, like this: - -```swift -Button("Try again") { - withAnimation { - selected = activities.randomElement() ?? "Archery" - } -} -.buttonStyle(.borderedProminent) -``` - -That will cause our button press to move between activities with a gentle fade. If you want, you can customize that animation by passing the animation you want to the `withAnimation()` call, like this: - -```swift -withAnimation(.easeInOut(duration: 1)) { - // ... -} -``` - -That’s an improvement, but we can do better! - -The fade happens because SwiftUI sees the background color, icon, and text changing, so it removes the old views and replaces it with new views. Earlier I made you create an inner `VStack` to house those three views, and now you can see why: we’re going to tell SwiftUI that these views can be identified as a single group, and that the group’s identifier can change over time. - -To make that happen, we need to start by defining some more program state inside our view. This will be the identifier for our inner `VStack`, and because it will change as our program runs we’ll use `@State`. Add this property next to `selected`: - -```swift -@State private var id = 1 -``` - -**Tip:** That’s more local view state, so it’s good practice to mark it with `private`. - -Next, we can tell SwiftUI to change that identifier every time our button is pressed, like this: - -```swift -Button("Try again") { - withAnimation(.easeInOut(duration: 1)) { - selected = activities.randomElement() ?? "Archery" - id += 1 - } -} -.buttonStyle(.borderedProminent) -``` - -Finally, we can use SwiftUI’s `id()` modifier to attach that identifier to the whole inner `VStack`, meaning that when the identifier changes SwiftUI should consider the whole `VStack` as new. This will make it animate the old `VStack` being removed and a new `VStack` being added, rather than just the individual views inside it. Even better, we can control how that add and remove transition happens using a `transition()` modifier, which has various built-in transitions we can use. - -So, add these two modifiers to the inner `VStack`, telling SwiftUI to identify the whole group using our `id` property, and animate its add and removal transitions with a slide: - -```swift -.transition(.slide) -.id(id) -``` - -Press Cmd+R to run your app one last time, and you should see that pressing “Try Again” now smoothly animates the old activity off the screen, and replaces it with a new one. It even overlaps animations if you press “Try Again” repeatedly! - - - -## Where now? - -We’ve covered a lot of SwiftUI basics in this tutorial, including text, images, buttons, stacks, animation, and even using `@State` to mark values that change over time. SwiftUI is capable of so much more, and can be used to build complex cross-platform apps if needed. - -If you’d like to continue learning SwiftUI, there are lots of free resources available. For example, [Apple publishes a wide variety of tutorials](https://developer.apple.com/tutorials/swiftui) covering essential topics, drawing and animation, app design, and more. We’ll also post links here on Swift.org to some other popular tutorials – we’re a big and welcoming community, and we’re glad to have you join! - -> The source code for this guide can be found [on GitHub](https://github.com/0xTim/swift-org-swiftui-tutorial) +SwiftUI is written with Apple platforms in mind, but it's possible to use Swift +to build mobile apps for other platforms too. Tools from other companies like +[Skip](https://skip.tools) provide support for running Swift on Android. diff --git a/getting-started/swiftui/tutorial.md b/getting-started/swiftui/tutorial.md new file mode 100644 index 000000000..49b26486a --- /dev/null +++ b/getting-started/swiftui/tutorial.md @@ -0,0 +1,409 @@ +--- +layout: page +title: Build Mobile Apps with Swift +--- + +In this tutorial you’re going to use Swift and SwiftUI to build a small app to recommend fun new activities to users. Along the way you’ll meet several of the basic components of a SwiftUI app, including text, images, buttons, shapes, stacks, and program state. + +To get started, you’ll need to [download Xcode from the Mac App Store](https://apps.apple.com/app/xcode/id497799835?mt=12). It’s free, and comes with Swift and all the other tools you need to follow this tutorial. + +Go ahead and launch Xcode once it’s installed, then select Create a new Xcode Project. Select the iOS tab at the top, then select the App template and press Next. + +**Tip:** Although we’ll be targeting iOS, the code we're writing will also work great on macOS. + +When making a new project, Xcode will ask you for a few pieces of information: + +- For Product Name, enter “WhyNotTry”. +- For Organization Identifier you can enter com.example. In real apps we’d normally enter our own domain name here, e.g. org.swift. +- For Interface make sure SwiftUI is selected. +- You can uncheck the boxes for Core Data and Include Tests; we won’t be using them here. + +![New Xcode Project]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/new-project.png) + +When you press Next, Xcode will ask where you want to save the project. You’re welcome to choose wherever suits you, but you might find your Desktop is easiest. Once that’s done, Xcode will create the new project for you, then open ContentView.swift for editing. This is where we’ll write all our code, and you’ll see some default SwiftUI code in there for us. + +![Initial SwiftUI project]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/initial-view.png) + +The example code Xcode made for us creates a new view called `ContentView`. Views are how SwiftUI represents our app’s user interface on the screen, and we can add custom layout and logic in there. + +On the right-hand side of Xcode, you’ll see a live preview of that code running – if you make a change to the code on the left, it will appear in the preview straight away. If you can't see the preview, follow [these instructions](https://developer.apple.com/documentation/swiftui/previews-in-xcode) to enable it. + +For example, try replacing the default `body` code with this: + +```swift +var body: some View { + Text("Hello, SwiftUI!") +} +``` + +![Hello SwiftUI]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/hello-swift-ui.png) + +You should see your preview update immediately, which makes for really fast prototyping while you work. This is a computed property called `body`, and SwiftUI will call that whenever it wants to display our user interface. + +## Building a static UI + +In this app we’re going to show the user a new activity they could try to keep fit, such as basketball, golf, and hiking. To make it a little more attractive, we’ll display each activity using its name, and also an icon representing the activity, then add a splash of color behind it. + +The main part of our user interface will be a circle showing the currently recommended activity. We can draw circles just by writing `Circle`, so replace the `Text("Hello, SwiftUI!")` view with this: + +```swift +Circle() +``` + +![SwiftUI Circle]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle.png) + +In your preview you’ll see a large black circle fills the available screen width. That’s a start, but it’s not quite right – we want some color in there, and ideally adding a little space on either side so it doesn’t look so tight. + +Both of these can be accomplished by calling methods on the `Circle` view. We call these *view modifiers* in SwiftUI because they modify the way the circle looks or works, and in this case we need to use the `fill()` modifier to color the circle, then the `padding()` modifier to add some space around it, like this: + +```swift +Circle() + .fill(.blue) + .padding() +``` + +![SwiftUI Circle with Color and Padding]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-color.png) + +The `.blue` color is one of several built-in options, such as `.red`, `.white`, and `.green`. These are all appearance aware, which means they look subtly different depending on the whether the device is in dark mode or light mode. + +Over that blue circle we’re going to place an icon showing the activity we recommend. iOS comes with several thousand free icons called *SF Symbols*, and there’s a [free app you can download that shows you all the options](https://developer.apple.com/sf-symbols/). Each of these icons is available in multiple weights, can be scaled up or down smoothly, and many can also be colored. + +Here, though, we want something nice and simple: we want just one icon placed over our circle. This means using another modifier called `overlay()`, which places one view over another. Modify your code to this: + +```swift +Circle() + .fill(.blue) + .padding() + .overlay( + Image(systemName: "figure.archery") + ) +``` + +![SwiftUI Circle with Icon]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-icon.png) + +You should see a small, black archery icon over our large, blue circle – it’s the right idea, but it doesn’t look great. + +What we really want is the archery icon to be much bigger, and also much more visible on that background. For that we need another two modifiers: `font()` to control the size of the icon, and `foregroundColor()` to change its color. Yes, we use a font modifier to control the icon’s size – SF Symbols like this one automatically scale with the rest of our text, which makes them really flexible. + +Adjust your `Image` code to this: + +```swift +Image(systemName: "figure.archery") + .font(.system(size: 144)) + .foregroundColor(.white) +``` + +![SwiftUI Circle with Icon Sized]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/swiftui-circle-icon-sized.png) + +**Tip:** That `font()` modifier asks for a 144-point system font, which is nice and big on all devices. + +That should now look a lot better. + +Next, let’s add some text below the image so it’s clear to the user what the suggestion is. You already met the `Text` view and the `font()` modifier, so you can add this code below the `Circle` code: + +```swift +Text("Archery!") + .font(.title) +``` + +Rather than using a fixed font size, that uses one of SwiftUI’s built in Dynamic Type sizes called `.title`. This means the font will grow or shrink depending on the user’s settings, which is usually a good idea. + +If everything has gone to plan, your code should look like this: + +```swift +var body: some View { + Circle() + .fill(.blue) + .padding() + .overlay( + Image(systemName: "figure.archery") + .font(.system(size: 144)) + .foregroundColor(.white) + ) + + Text("Archery!") + .font(.title) +} +``` + +![Circle With Title Text]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/circle-with-title.png) + +However, what you see in Xcode’s preview probably won’t match what you were expecting: you’ll see the same icon as before, but no text. What gives? + +The problem here is that we’ve told SwiftUI our user interface will have two views inside – the circle and some text – but we haven’t told it how to arrange them. Do we want them side by side? One above the other? Or in some other kind of layout? + +We get to choose, but I think here a vertical layout will look better. In SwiftUI we get that with a new view type called `VStack`, which is placed *around* our current code, like this: + +```swift +VStack { + Circle() + .fill(.blue) + .padding() + .overlay( + Image(systemName: "figure.archery") + .font(.system(size: 144)) + .foregroundColor(.white) + ) + + Text("Archery!") + .font(.title) +} +``` + +And now you should see the layout you expected earlier: our archery icon above the text “Archery!”. + +![Circle With Title Text in a VStack]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/circle-with-title-vstack.png) + +That’s much better! + +To finish up our first pass at this user interface, we can add a title at the top. We already have a `VStack` that allows us to position views one above the other, but I don’t want the title inside there too because later on we’ll be adding some animation for that part of our screen. + +Fortunately, SwiftUI lets us nest stacks freely, meaning that we can place a `VStack` inside another `VStack` to get the exact behavior we want. So, change your code to this: + +```swift +VStack { + Text("Why not try…") + .font(.largeTitle.bold()) + + VStack { + Circle() + .fill(.blue) + .padding() + .overlay( + Image(systemName: "figure.archery") + .font(.system(size: 144)) + .foregroundColor(.white) + ) + + Text("Archery!") + .font(.title) + } +} +``` + +That makes the new text have a large title font, and also makes it bold so it stands out better as a real title for our screen. + +![Why Not Try Title Added]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/why-not-try-title.png) + +Now we have two `VStack` views: an inner one that holds the circle and “Archery!” text, and an outer one that adds a title around the inner `VStack`. This will be very helpful later on when we add animation! + +## Bringing it to life + +As much fun as archery is, this app really needs to suggest a random activity to users rather than always showing the same thing. That means adding two new properties to our view: one to store the array of possible activities, and one to show whichever one is currently being recommended. + +SF Symbols has lots of interesting activities to choose from, so I’ve picked out a handful that work well here. Our `ContentView` struct already has a `body` property containing our SwiftUI code, but we want to add new properties outside that. So, change your code to this: + +```swift +struct ContentView: View { + var activities = ["Archery", "Baseball", "Basketball", "Bowling", "Boxing", "Cricket", "Curling", "Fencing", "Golf", "Hiking", "Lacrosse", "Rugby", "Squash"] + + var selected = "Archery" + + var body: some View { + // ... + } +} +``` + +**Important:** Notice how the `activities` and `selected` properties are *inside* the struct – that means they belong to `ContentView`, rather than just being free-floating variables in our program. + +That creates an array of various activity names, and selects archery as the default. Now we can use the selected activity in our UI using string interpolation – we can place the `selected` variable directly inside strings. + +For the activity name this is straightforward: + +```swift +Text("\(selected)!") + .font(.title) +``` + +For the image this is a little more complicated, because we need to prefix it with `figure.` then lowercase the activity name – we want `figure.archery` rather than `figure.Archery`, otherwise the SF Symbol won’t be loaded. + +So, change your `Image` code this: + +```swift +Image(systemName: "figure.\(selected.lowercased())") +``` + +Those changes mean our UI will display whatever the `selected` property is set to, so can see it all change if you place a new string in that property: + +```swift +var selected = "Baseball" +``` + +![Showing Baseball]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/baseball.png) + +Of course, we want that to change *dynamically* rather than having to edit the code each time, so we’re going to add a button below our inner `VStack` that will change the selected activity every time it’s pressed. This is still inside the outer `VStack`, though, which means it will be arranged below the title and activity icon. + +Add this code now: + +```swift +Button("Try again") { + // change activity +} +.buttonStyle(.borderedProminent) +``` + +![Try Again Button]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/try-again-button.png) + +So, your structure should be this: + +```swift +VStack { + // "Why not try…" text + + // Inner VStack with icon and activity name + + // New button code +} +``` + +The new button code does three things: + +1. We create the `Button` by passing in a title to show as the button’s label. +2. The `// change activity` comment is code that will be run when the button is pressed. +3. The `buttonStyle()` modifier tells SwiftUI we want this button to stand out, so you’ll see it appear in a blue rectangle with white text. + +Just having a comment as the button’s action isn’t very interesting – really we want to make it set `selected` to a random element from the `activities` array. We can pick a random element from the array by calling the helpfully named `randomElement()` method on it, so replace the comment with this: + +```swift +selected = activities.randomElement() +``` + +That code *looks* right, but it will actually cause compiler errors. We’re telling Swift to pick a random element from the array and place it into the `selected` property, but there’s no way for Swift to be sure there’s anything in that array – it could be empty, in which case there’s no random element to return. + +![Random Element Error]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/random-element-error.png) + +Swift calls these *optionals*: `randomElement()` won’t return a regular string, it will return an *optional* string. This means the string might not be there, so it’s not safe to assign to the `selected` property. + +Even though we know the array will never be empty – it will *always* have activities in there – we can give Swift a sensible default value to use just in case the array happens to be empty in the future, like this: + +```swift +selected = activities.randomElement() ?? "Archery" +``` + +That partly fixes our code, but Xcode will still be showing an error. The problem now is that SwiftUI doesn’t like us changing our program’s state right inside our view structs without warning – it wants us to mark all the mutable state ahead of time, so it knows to watch for changes. + +![Non-@State mutating]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/non-state-mutating.png) + +This is done by writing `@State` before any view properties that will change, like this: + +```swift +@State var selected = "Baseball" +``` + +This is called a *property wrapper*, meaning that it wraps our `selected` property with some extra logic. The `@State` property wrapper allows us to change view state freely, but it also automatically watches its property for changes so that it can make sure the user interface stays up to date with the latest values. + +That fixes the two errors in our code, so you can now press Cmd+R to build and run your app in the iOS simulator. It will suggest baseball by default, but every time you press “Try again” you’ll see it change. + +Running The App in the Simulator + +## Adding some polish + +Before we’re done with this project, let’s add a handful more tweaks to make it better. + +First, an easy one: Apple recommends that local view state always be marked with `private` access control. In larger projects, this means you can’t accidentally write code that reads one view’s local state from another, which helps keep your code easier to understand. + +This means modifying the `selected` property like so: + +```swift +@State private var selected = "Baseball" +``` + +Second, rather than always showing a blue background, we can pick a random color each time. This takes two steps, starting with a new property of all the colors we want to select from – put this next to the `activities` property: + +```swift +var colors: [Color] = [.blue, .cyan, .gray, .green, .indigo, .mint, .orange, .pink, .purple, .red] +``` + +Now we can change our circle’s `fill()` modifier to use `randomElement()` on that array, or `.blue` if somehow the array ends up being empty: + +```swift +Circle() + .fill(colors.randomElement() ?? .blue) +``` + +Third, we can separate the activity `VStack` and “Try again” button by adding a new SwiftUI view between them, called `Spacer`. This is a flexible space that automatically expands, which means it will push our activity icon to the top of the screen, and the button to the bottom. + +Insert it between the two, like this: + +```swift +VStack { + // current Circle/Text code +} + +Spacer() + +Button("Try again") { + // ... +} +``` + +If you add multiple spacers, they will divide the space equally between them. If you try placing a second spacer before the “Why not try…” text you’ll see what I mean – SwiftUI will create and equal amount of space above the text and below the activity name. + +![View With Spacers]({{site.url}}/assets/images/getting-started-guides/swiftui-ios/spacers.png) + +And fourth, it would be nice if the change between activities was smoother, which we can do by animating the change. In SwiftUI, this is done by wrapping changes we want to animate with a call to the `withAnimation()` function, like this: + +```swift +Button("Try again") { + withAnimation { + selected = activities.randomElement() ?? "Archery" + } +} +.buttonStyle(.borderedProminent) +``` + +That will cause our button press to move between activities with a gentle fade. If you want, you can customize that animation by passing the animation you want to the `withAnimation()` call, like this: + +```swift +withAnimation(.easeInOut(duration: 1)) { + // ... +} +``` + +That’s an improvement, but we can do better! + +The fade happens because SwiftUI sees the background color, icon, and text changing, so it removes the old views and replaces it with new views. Earlier I made you create an inner `VStack` to house those three views, and now you can see why: we’re going to tell SwiftUI that these views can be identified as a single group, and that the group’s identifier can change over time. + +To make that happen, we need to start by defining some more program state inside our view. This will be the identifier for our inner `VStack`, and because it will change as our program runs we’ll use `@State`. Add this property next to `selected`: + +```swift +@State private var id = 1 +``` + +**Tip:** That’s more local view state, so it’s good practice to mark it with `private`. + +Next, we can tell SwiftUI to change that identifier every time our button is pressed, like this: + +```swift +Button("Try again") { + withAnimation(.easeInOut(duration: 1)) { + selected = activities.randomElement() ?? "Archery" + id += 1 + } +} +.buttonStyle(.borderedProminent) +``` + +Finally, we can use SwiftUI’s `id()` modifier to attach that identifier to the whole inner `VStack`, meaning that when the identifier changes SwiftUI should consider the whole `VStack` as new. This will make it animate the old `VStack` being removed and a new `VStack` being added, rather than just the individual views inside it. Even better, we can control how that add and remove transition happens using a `transition()` modifier, which has various built-in transitions we can use. + +So, add these two modifiers to the inner `VStack`, telling SwiftUI to identify the whole group using our `id` property, and animate its add and removal transitions with a slide: + +```swift +.transition(.slide) +.id(id) +``` + +Press Cmd+R to run your app one last time, and you should see that pressing “Try Again” now smoothly animates the old activity off the screen, and replaces it with a new one. It even overlaps animations if you press “Try Again” repeatedly! + + + +## Where now? + +We’ve covered a lot of SwiftUI basics in this tutorial, including text, images, buttons, stacks, animation, and even using `@State` to mark values that change over time. SwiftUI is capable of so much more, and can be used to build complex cross-platform apps if needed. + +If you’d like to continue learning SwiftUI, there are lots of free resources available. For example, [Apple publishes a wide variety of tutorials](https://developer.apple.com/tutorials/swiftui) covering essential topics, drawing and animation, app design, and more. We’ll also post links here on Swift.org to some other popular tutorials – we’re a big and welcoming community, and we’re glad to have you join! + +> The source code for this guide can be found [on GitHub](https://github.com/0xTim/swift-org-swiftui-tutorial) diff --git a/index.md b/index.md index e77bc5beb..3f0f5be3c 100644 --- a/index.md +++ b/index.md @@ -36,16 +36,16 @@ atom: true
  • - + - Get started + Language tour
  • - Read the docs + Documentation
  • @@ -58,82 +58,159 @@ atom: true -## Use Cases +## Getting Started with Swift + +

    + Swift runs on most modern operating systems, including macOS, Windows, Linux, + iOS, Android and embedded devices. It's a versatile language for many + different scenarios: +

    -## Getting Involved +Regardless of how you're using Swift, you'll find [lots of resources for learning Swift](https://developer.apple.com/swift/pathway/), whether you're taking your first steps into code or are a seasoned developer switching from another language. We also have comprehensive [Swift resources for educators](https://education.apple.com/learning-center/T021340A-en_US). -Everyone is welcome to contribute to Swift. Contributing doesn’t just mean writing code or submitting pull request — there are many different ways for you to get involved, including answering questions on the forums, reporting or triaging bugs, and participating in the Swift evolution process. +## Make Connections -No matter how you want to get involved, we ask that you first learn what’s expected of anyone who participates in the project by reading the [Community Overview](/community/). If you’re contributing code, you should also know how to build and run Swift from the repository, as described in [Source Code](/documentation/source-code/). +Everyone is welcome to participate in improving Swift. There are many different ways to get involved: you can host or attend a Swift event; you can post or answer questions on the forums; and you can play a role in Swift's development. -## What's New - -Stay up-to-date with the latest in the Swift community. - -