{
  "$type": "site.standard.document",
  "description": "In the last few weeks I’ve been trying to catch up on SwiftUI - watching WWDC videos, reading tutorials. Not the new stuff that was announced 2 months ago though - but the things that people have been using for the past year.\n\nLast June, like everyone else I immediately started playing with SwiftUI like a kid with a new box of Legos. In the first month I managed to build a sample Mac app for switching dark mode in apps. However, after that I got busy with some other things, and never really got back to SwiftUI until recently, so by the time the “version 2” was announced at the online-only WWDC, I’ve already forgotten most of it. So in order to not get this all mixed up, I decided to first remember everything about the existing version, before I look at the new stuff.\n\nBack then, when I was watching all the videos and doing the tutorial, I was taking a lot of notes about all the components, modifiers and APIs you can use, every single detail I noticed on a slide. However, I was surprised to see how many of those things I wrote down don’t work anymore. After the first version that most people have played with and that the videos are based on, there were apparently a lot of changes in subsequent betas (especially in betas 3 to 5). Classes and modifiers changing names, initializers taking different parameters, some things redesigned completely.\nAnd the problem is that all those old APIs are still there in the WWDC videos from last year. But WWDC videos are usually a very good source of knowledge, people come back to them years later looking for information that can’t be found in the docs, Apple even often references videos from previous years in new videos, because they naturally can’t repeat all information every year.\n\nThis was bothering me enough that I decided to spend some time collecting all the major changes in the APIs that were presented in June 2019, but were changed later in one place. If you’re reading this in 2021 or 2022 (hopefully that damn pandemic is over!), watching the first SwiftUI videos and wondering why things don’t work when typed into Xcode - this is for you.\n\nHere’s a list of what was changed between the beta 1 from June 2019 and the final version from September (includes only things that were mentioned in videos or tutorials):\n\nNavigationButton\n\nAppeared in: “Building Lists and Navigation” tutorial, “Platforms State of the Union”\n\nForEach(store.trails) { trail in\n    NavigationButton(destination: TrailDetailView(trail)) {\n        TrailCell(trail)\n    }\n}\n\nReplaced with: NavigationLink\n\nForEach(store.trails) { trail in\n    NavigationLink(destination: TrailDetailView(trail)) {\n        TrailCell(trail)\n    }\n}\n\nPresentationButton / PresentationLink\n\nAppeared in: “Composing Complex Interfaces” tutorial, “Platforms State of the Union”\n\n.navigationBarItems(trailing:\n    PresentationButton(\n        Image(systemName: \"person.crop.circle\"),\n        destination: ProfileScreen()\n    )\n)\n\nReplaced with: PresentationLink, which was lat…",
  "path": "/2020/08/17/swiftui-beta/",
  "publishedAt": "2020-08-17T12:26:14Z",
  "site": "at://did:plc:oio4hkxaop4ao4wz2pp3f4cr/site.standard.publication/3mn5mackuba26",
  "tags": [
    "Cocoa",
    "iPhone",
    "Mac",
    "SwiftUI"
  ],
  "textContent": "In the last few weeks I’ve been trying to catch up on SwiftUI - watching WWDC videos, reading tutorials. Not the new stuff that was announced 2 months ago though - but the things that people have been using for the past year.\n\nLast June, like everyone else I immediately started playing with SwiftUI like a kid with a new box of Legos. In the first month I managed to build a sample Mac app for switching dark mode in apps. However, after that I got busy with some other things, and never really got back to SwiftUI until recently, so by the time the “version 2” was announced at the online-only WWDC, I’ve already forgotten most of it. So in order to not get this all mixed up, I decided to first remember everything about the existing version, before I look at the new stuff.\n\nBack then, when I was watching all the videos and doing the tutorial, I was taking a lot of notes about all the components, modifiers and APIs you can use, every single detail I noticed on a slide. However, I was surprised to see how many of those things I wrote down don’t work anymore. After the first version that most people have played with and that the videos are based on, there were apparently a lot of changes in subsequent betas (especially in betas 3 to 5). Classes and modifiers changing names, initializers taking different parameters, some things redesigned completely.\nAnd the problem is that all those old APIs are still there in the WWDC videos from last year. But WWDC videos are usually a very good source of knowledge, people come back to them years later looking for information that can’t be found in the docs, Apple even often references videos from previous years in new videos, because they naturally can’t repeat all information every year.\n\nThis was bothering me enough that I decided to spend some time collecting all the major changes in the APIs that were presented in June 2019, but were changed later in one place. If you’re reading this in 2021 or 2022 (hopefully that damn pandemic is over!), watching the first SwiftUI videos and wondering why things don’t work when typed into Xcode - this is for you.\n\nHere’s a list of what was changed between the beta 1 from June 2019 and the final version from September (includes only things that were mentioned in videos or tutorials):\n\nNavigationButton\n\nAppeared in: “Building Lists and Navigation” tutorial, “Platforms State of the Union”\n\nForEach(store.trails) { trail in\n    NavigationButton(destination: TrailDetailView(trail)) {\n        TrailCell(trail)\n    }\n}\n\nReplaced with: NavigationLink\n\nForEach(store.trails) { trail in\n    NavigationLink(destination: TrailDetailView(trail)) {\n        TrailCell(trail)\n    }\n}\n\nPresentationButton / PresentationLink\n\nAppeared in: “Composing Complex Interfaces” tutorial, “Platforms State of the Union”\n\n.navigationBarItems(trailing:\n    PresentationButton(\n        Image(systemName: \"person.crop.circle\"),\n        destination: ProfileScreen()\n    )\n)\n\nReplaced with: PresentationLink, which was later removed and replaced with .sheet:\n\n.navigationBarItems(trailing:\n    Button(action: { self.showingProfile.toggle() }) {\n        Image(systemName: \"person.crop.circle\")\n    }\n)\n.sheet(isPresented: $showingProfile) {\n    ProfileScreen()\n}\n\nSegmentedControl\n\nAppeared in: “Working With UI Controls” tutorial\n\nSegmentedControl(selection: $profile.seasonalPhoto) {\n    ForEach(Profile.Season.allCases) { season in\n        Text(season.rawValue).tag(season)\n    }\n}\n\nReplaced with: Picker with SegmentedPickerStyle()\n\nPicker(\"Seasonal Photo\", selection: $profile.seasonalPhoto) {\n    ForEach(Profile.Season.allCases) { season in\n        Text(season.rawValue).tag(season)\n    }\n}\n.pickerStyle(SegmentedPickerStyle())\n\nTabbedView and tabItemLabel\n\nAppeared in: “SwiftUI Essentials”, “SwiftUI on All Devices”\n\nTabbedView {\n    ExploreView().tabItemLabel(Text(\"Explore\"))\n    HikesView().tabItemLabel(Text(\"Hikes\"))\n    ToursView().tabItemLabel(Text(\"Tours\"))\n}\n\nReplaced with: TabView and tabItem:\n\nTabView {\n    ExploreView().tabItem { Text(\"Explore\") }\n    HikesView().tabItem { Text(\"Hikes\") }\n    ToursView().tabItem { Text(\"Tours\") }\n}\n\nDatePicker(selection:minimumDate:maximumDate:displayedComponents:)\n\nAppeared in: “Working With UI Controls” tutorial\n\nDatePicker(\n    $profile.goalDate,\n    minimumDate: startDate,\n    maximumDate: endDate,\n    displayedComponents: .date\n)\n\nThe minimumDate and maximumDate parameters were replaced with a ClosedRange<Date>, and a label parameter was added:\n\nDatePicker(\n    selection: $profile.goalDate,\n    in: startDate...endDate,\n    displayedComponents: .date\n) {\n  Text(\"Goal Date\")\n}\n\nYou can also use this shorthand variant with a string label:\n\nDatePicker(\n    \"Goal Date\",\n    selection: $profile.goalDate,\n    in: startDate...endDate,\n    displayedComponents: .date\n)\n\nTextField(_:placeholder:), SecureField(_:placeholder:)\n\nAppeared in: “Platforms State of the Union”, “Working With UI Controls” tutorial\n\nTextField($profile.username, placeholder: Text(“Username”))\nSecureField($profile.password, placeholder: Text(“Password”))\n\nReplaced with:\n\nTextField(\"Username\", text: $profile.username)\nSecureField(\"Password\", text: $profile.password)\n\nScrollView(showsHorizontalIndicator: false)\n\nAppeared in: “Composing Complex Interfaces” tutorial\n\nScrollView(showsHorizontalIndicator: false) {\n    HStack(alignment: .top, spacing: 0) {\n        ForEach(self.items) { landmark in\n            CategoryItem(landmark: landmark)\n        }\n    }\n}\n\nReplaced with: ScrollView(.horizontal, showsIndicators: false)\n\nScrollView(.horizontal, showsIndicators: false) {\n    HStack(alignment: .top, spacing: 0) {\n        ForEach(self.items) { landmark in\n            CategoryItem(landmark: landmark)\n        }\n    }\n}\n\nIt doesn’t seem to be possible now to have a scroll view that scrolls in both directions, but only shows indicators on one side (?)\n\nList(_:action:)\n\nAppeared in: Keynote, “Platforms State of the Union”\n\nList(model.items, action: model.selectItem) { item in\n    Image(item.image)\n    Text(item.title)\n}\n\nRemoved sometime in later betas - you can use selection: instead:\n\nList(model.items, selection: $selectedItem) { item in\n    Image(item.image)\n    Text(item.title)\n}\n\nText.color()\n\nAppeared in: “Composing Complex Interfaces” tutorial, Keynote, “Platforms State of the Union”\n\nText(item.subtitle).color(.gray)\n\nReplaced with: .foregroundColor()\n\nText(item.subtitle).foregroundColor(.gray)\n\nText.lineLimit(nil)\n\nAppeared in: “Platforms State of the Union”, “SwiftUI on All Devices”\n\nText(trail.description)\n    .lineLimit(nil)\n\nText used to have a default line limit of 1, so if you wanted to have a multi-line text control showing some longer text, you had to add .lineLimit(nil).\n\nThis was changed later and now no limit is the default. Instead, you may need to add .lineLimit(1) if you want to make sure that label contents don’t overflow into a second line if it’s too long.\n\nText(verbatim:)\n\nAppeared in: “Building Lists and Navigation” tutorial, “SwiftUI on All Devices”\n\nText(verbatim: landmark.name)\n\nIt’s unclear if and when anything has changed in this API since beta 1. The current docs say that:\n\nText(\"string\") used with a literal string is automatically localized\nText(model.field) used with variable is not localized\nText(verbatim: \"string\") should be used with a literal string that should not be localized\n\nSo verbatim: shouldn’t (or can’t) be used with variables like in the code above anymore, since in this variant the text will not be translated anyway. The parameter was removed from later versions of the tutorial code.\n\nIf you do want to localize a text that comes from a model property, use Text(LocalizedStringKey(value)).\n\n.animation(…)\n\nAppeared in: “Animating Views and Transitions” tutorial, “Introducing SwiftUI”, “SwiftUI on All Devices”\n\nThe .animation modifier has a number of different animation styles that you can choose from. This set of options has changed between the first beta and the final version.\n\nIn beta 1 you could do:\n\n.animation(.basic())\n.animation(.basic(duration: 5.0, curve: .linear))\n.animation(.basic(duration: 5.0, curve: .easeIn))\n.animation(.basic(duration: 5.0, curve: .easeInOut))\n.animation(.basic(duration: 5.0, curve: .easeOut))\n\n.animation(.default)\n.animation(.empty)\n\n.animation(.fluidSpring())\n.animation(.fluidSpring(\n  stiffness: 1.0, dampingFraction: 1.0, blendDuration: 1.0, timestep: 1.0, idleThreshold: 1.0\n))\n\n.animation(.spring())\n.animation(.spring(mass: 1.0, stiffness: 1.0, damping: 1.0, initialVelocity: 1.0))\n\nThe .basic animations were replaced with options named after the selected curve:\n\n.animation(.linear)\n.animation(.linear(duration: 1.0))\n.animation(.easeIn)\n.animation(.easeIn(duration: 1.0))\n.animation(.easeInOut)\n.animation(.easeInOut(duration: 1.0))\n.animation(.easeOut)\n.animation(.easeOut(duration: 1.0))\n\n(I’m not sure what .basic() without any parameters used to do exactly.)\n\nWhat was called .spring is now .interpolatingSpring:\n\n.animation(.interpolatingSpring(mass: 1.0, stiffness: 1.0, damping: 1.0, initialVelocity: 1.0))\n\nAnd .fluidSpring is now either .spring or .interactiveSpring\n\n.animation(.spring())\n.animation(.spring(response: 1.0, dampingFraction: 1.0, blendDuration: 1.0))\n.animation(.interactiveSpring())\n.animation(.interactiveSpring(response: 1.0, dampingFraction: 1.0, blendDuration: 1.0))\n\n.default is still available (I’m not sure what it does though) and .empty was removed.\n\n.background(_:cornerRadius:)\n\nAppeared in: “Platforms State of the Union”, “SwiftUI Essentials”\n\nText(\"🥑🍞\")\n    .background(Color.green, cornerRadius: 12)\n\nRemoved later - you can use separate .background and .cornerRadius modifiers to achieve the same effect:\n\nText(\"🥑🍞\")\n    .background(Color.green)\n    .cornerRadius(12)\n\n.identified(by:) method on collections\n\nAppeared in: “Building Lists and Navigation” tutorial, “Platforms State of the Union”, “SwiftUI on All Devices”\n\nForEach(categories.keys.identified(by: \\.self)) { key in\n    CategoryRow(categoryName: key)\n}\n\nReplaced with: id: parameter\n\nForEach(categories.keys, id: \\.self) { key in\n    CategoryRow(categoryName: key)\n}\n\n.listStyle, .pickerStyle etc. with enum cases\n\nAppeared in: “Introducing SwiftUI”, “SwiftUI Essentials”\n\n.listStyle(.grouped)\n.pickerStyle(.radioGroup)\n.textFieldStyle(.roundedBorder)\n\nReplaced with: creating instances of specific types\n\n.listStyle(GroupedListStyle())\n.pickerStyle(RadioGroupPickerStyle())\n.textFieldStyle(RoundedBorderTextFieldStyle())\n\n.navigationBarItem(title:)\n\nAppeared in: “Platforms State of the Union”\n\nNavigationView {\n    List {\n        ...\n    }\n    .navigationBarItem(title: Text(\"Explore\"))\n}\n\nReplaced with: .navigationBarTitle\n\nNavigationView {\n    List {\n        ...\n    }\n    .navigationBarTitle(\"Explore\")\n}\n\n.onPaste, .onPlayPause, .onExit\n\nAppeared in: “Integrating SwiftUI”\n\n.onPaste(of: types) { provider in\n    self.handlePaste(provider)\n}\n.onPlayPause {\n    self.pause()\n}\n.onExit {\n    self.close()\n}\n\nReplaced with .onPasteCommand, .onPlayPauseCommand, .onExitCommand\n\n.onPasteCommand(of: types) { provider in\n    self.handlePaste(provider)\n}\n.onPlayPauseCommand {\n    self.pause()\n}\n.onExitCommand {\n    self.close()\n}\n\n.tapAction\n\nAppeared in: “Platforms State of the Union”, “Introducing SwiftUI”, “SwiftUI on All Devices”\n\nImage(room.imageName)\n    .tapAction { self.zoomed.toggle() }\n\nMacLandmarkRow(landmark: landmark)\n    .tapAction(count: 2) { self.showDetail(landmark) }\n\nReplaced with: .onTapGesture\n\nImage(room.imageName)\n    .onTapGesture { self.zoomed.toggle() }\n\nMacLandmarkRow(landmark: landmark)\n    .onTapGesture(count: 2) { self.showDetail(landmark) }\n\nBindableObject and didChange\n\nAppeared in: “Handling User Input” tutorial, “Introducing SwiftUI”, “Data Flow Through SwiftUI”\n\nclass UserData: BindableObject {\n    let didChange = PassthroughSubject<UserData, Never>()\n\n    var showFavorites = false {\n        didSet {\n            didChange.send(self)\n        }\n    }\n}\n\nReplaced with: ObservableObject with objectWillChange (needs to be called before the change!). objectWillChange is automatically included, so you don’t need to declare it.\n\nclass UserData: ObservableObject {\n    var showFavorites = false {\n        willSet {\n            objectWillChange.send(self)\n        }\n    }\n}\n\nIn simple cases, you can use the @Published attribute instead which handles this automatically for you:\n\nclass UserData: ObservableObject {\n    @Published var showFavorites = false\n}\n\nBindableObject was used together with:\n\n@ObjectBinding\n\nAppeared in: “Handling User Input” tutorial, “Introducing SwiftUI”, “Data Flow Through SwiftUI”\n\n@ObjectBinding var store = RoomStore()\n\nReplaced with: @ObservedObject\n\n@ObservedObject var store = RoomStore()\n\nCollection methods on Bindings\n\nI don’t think this was mentioned in any talks or tutorials, but there were some tweets going around last June showing how you can do some cool tricks with bindings by calling methods on them, e.g.:\n\nToggle(landmark.name, isOn: $favorites.contains(landmarkID))\n\nSadly, this was removed in a later beta. The release notes include some extension code that you can add to your project to reimplement something similar.\n\nCommand\n\nAppeared in: “SwiftUI on All Devices”\n\nextension Command {\n    static let showExplore = Command(Selector(\"showExplore\"))\n}\n\n.onCommand(.showExplore) { self.selectedTab = .explore }\n\nReplaced with: using Selector directly\n\n.onCommand(Selector(\"showExplore\")) { self.selectedTab = .explore }\n\nLength\n\nAppeared in: “Animating Views and Transitions” tutorial\n\nvar heightRatio: Length {\n    max(Length(magnitude(of: range) / magnitude(of: overallRange)), 0.15)\n}\n\nReplaced with: CGFloat\n\nvar heightRatio: CGFloat {\n    max(CGFloat(magnitude(of: range) / magnitude(of: overallRange)), 0.15)\n}\n\n#if DEBUG\n\nAppeared in: all tutorials, “Platforms State of the Union”, “Introducing SwiftUI”\n\nThis was automatically added around the preview definition in all SwiftUI view files created in beta versions of Xcode 11:\n\n#if DEBUG\nstruct CircleImage_Previews: PreviewProvider {\n    static var previews: some View {\n        CircleImage()\n    }\n}\n#endif\n\nIt was removed from the templates in one of the final betas - you no longer need to add that:\n\nstruct CircleImage_Previews: PreviewProvider {\n    static var previews: some View {\n        CircleImage()\n    }\n}",
  "title": "SwiftUI betas - what changed before 1.0",
  "updatedAt": "2025-06-30T01:49:16Z"
}