Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How to use SwiftUI for rapid development

How to use SwiftUI for rapid development

SwiftUI is a framework for declarative UI structure design across all Apple Platforms. It was introduced during WWDC 2019 and, since then, has been significantly updated both in framework and Swift language side.

It is rapidly becoming the de facto standard for UI development for Apple platforms, including iOS. We will see how we can start using SwiftUI and learn about this remarkable framework during the talk. We will create a working prototype in minutes and see how to use it in large applications.

As with every technology, we need to talk about tradeoffs. SwiftUI is a very opinionated framework and has quite strict guidelines. It is missing a lot of things that we have been using with UIKit. We are going to look at some problems and how to overcome them.

Kristaps Grinbergs

August 25, 2021
Tweet

More Decks by Kristaps Grinbergs

Other Decks in Programming

Transcript

  1. @State var pickedApples = 0 var body: some View {

    NavigationView { VStack(spacing: 30) { Text("🍎 : \(pickedApples)") NavigationLink("Pick apples") { PickApplesView(pickedApples: $pickedApples) } } } } @Binding var pickedApples: Int var body: some View { VStack(spacing: 30) { Text("Picked apples: \(pickedApples)") Button("Pick an apple 🍎") { pickedApples += 1 } } }
  2. class FruitBasketViewModel: ObservableObject { @Published var pickedApples = 0 func

    pickAnApple() { pickedApples += 1 } } @StateObject var fruitBasketViewModel = FruitBasketViewModel()
  3. class FruitBasketViewModel: ObservableObject { //@ObservedObject var fruitBasketViewModels: FruitBasketViewModel var body:

    some View { VStack(spacing: 30) { Text("🍎 : \(fruitBasketViewModel.pickedApples)") Button("Pick an apple", action: fruitBasketViewModel.pickAnApple) } }
  4. @EnvironmentObject var fruitBasketViewModel: FruitBasketViewModel var body: some View { VStack(spacing:

    30) { Text("🍎 : \(fruitBasketViewModel.pickedApples)") Button("Pick an apple", action: fruitBasketViewModel.pickAnApple) } } FruitBasketWithEnvironmentObject()
  5. struct ContentView: View { @State private var positionX: CGFloat =

    70 @State private var positionY: CGFloat = 70 var body: some View { Image("chuck-norris") .resizable() .scaledToFit() .frame(width: 100) .position(x: positionX, y: positionY) .onTapGesture { withAnimation(.easeInOut(duration: 1.5)) { if positionX == 70, positionY == 70 { positionY = UIScreen.height - 120 positionX = UIScreen.width - 100 } else { positionY = 70 positionX = 70 } } } } }
  6. struct ContentView: View { @State private var navigationIsActive = false

    var body: some View { NavigationView { VStack { NavigationLink(destination: SecondView(), isActive: $navigationIsActive) { EmptyView() } Button("Navigate") { navigationIsActive = true } } .navigationTitle("First View") } } }
  7. struct ContentView: View { @State private var navigationIsActive = false

    var body: some View { NavigationView { VStack { NavigationLink(destination: SecondView(), isActive: $navigationIsActive) { EmptyView() } Button("Navigate") { navigationIsActive = true } } .navigationTitle("First View") } } }
  8. struct ContentView: View { @State private var navigationIsActive = false

    var body: some View { NavigationView { VStack { NavigationLink(destination: SecondView(), isActive: $navigationIsActive) { EmptyView() } Button("Navigate") { navigationIsActive = true } } .navigationTitle("First View") } } }
  9. struct ContentView: View { @State private var navigationIsActive = false

    var body: some View { NavigationView { VStack { NavigationLink(destination: SecondView(), isActive: $navigationIsActive) { EmptyView() } Button("Navigate") { navigationIsActive = true } } .navigationTitle("First View") } } }
  10. enum NavigationViews: Hashable { case second case third } struct

    TagNavigationView: View { @State private var navigationToView: NavigationViews? var body: some View { NavigationView { VStack(spacing: 20) { NavigationLink(destination: SecondView(), tag: .second, selection: $navigationToView) { EmptyView() } NavigationLink(destination: ThirdView(), tag: .third, selection: $navigationToView) { EmptyView() } Button("Navigate to Second View") { navigationToView = .second } Button("Navigate to Third View") { navigationToView = .third } } } .navigationTitle("First View") } }
  11. enum NavigationViews: Hashable { case second case third } struct

    TagNavigationView: View { @State private var navigationToView: NavigationViews? var body: some View { NavigationView { VStack(spacing: 20) { NavigationLink(destination: SecondView(), tag: .second, selection: $navigationToView) { EmptyView() } NavigationLink(destination: ThirdView(), tag: .third, selection: $navigationToView) { EmptyView() } Button("Navigate to Second View") { navigationToView = .second } Button("Navigate to Third View") { navigationToView = .third } } } .navigationTitle("First View") } }
  12. enum NavigationViews: Hashable { case second case third } struct

    TagNavigationView: View { @State private var navigationToView: NavigationViews? var body: some View { NavigationView { VStack(spacing: 20) { NavigationLink(destination: SecondView(), tag: .second, selection: $navigationToView) { EmptyView() } NavigationLink(destination: ThirdView(), tag: .third, selection: $navigationToView) { EmptyView() } Button("Navigate to Second View") { navigationToView = .second } Button("Navigate to Third View") { navigationToView = .third } } } .navigationTitle("First View") } }
  13. enum NavigationViews: Hashable { case second case third } struct

    TagNavigationView: View { @State private var navigationToView: NavigationViews? var body: some View { NavigationView { VStack(spacing: 20) { NavigationLink(destination: SecondView(), tag: .second, selection: $navigationToView) { EmptyView() } NavigationLink(destination: ThirdView(), tag: .third, selection: $navigationToView) { EmptyView() } Button("Navigate to Second View") { navigationToView = .second } Button("Navigate to Third View") { navigationToView = .third } } } .navigationTitle("First View") } }
  14. struct ShowSheetView: View { @State private var showSheet = false

    var body: some View { Button("Show Sheet") { showSheet = true } .sheet(isPresented: $showSheet) { Text("Sheet Content") } } }
  15. extension Sheet { @ViewBuilder func modalView(with binding: Binding<Sheet?>) -> some

    View { switch self { case .info: InfoView(activeSheet: binding) case .settings: SettingsView(activeSheet: binding) } } }
  16. struct ContentView: View { @State var activeSheet: Sheet? var body:

    some View { VStack(spacing: 50) { Text("Main View") .font(.largeTitle) Button(action: { activeSheet = .info }, label: { Label("Show Info View", systemImage: "info.circle") }) Button(action: { activeSheet = .settings }, label: { Label("Show Settings View", systemImage: "gear") }) } .sheet(item: $activeSheet) { $0.modalView(with: $activeSheet) } } }
  17. struct ContentView: View { @State private var showSheet = false

    @State private var counter = 0 var body: some View { VStack { Text(counter.description) Button("Show Sheet") { showSheet = true } } .sheet(isPresented: $showSheet) { UpdateCounterView(counter: $counter) } } }
  18. struct ContentView: View { @State private var showSheet = false

    @State private var counter = 0 var body: some View { VStack { Text(counter.description) Button("Show Sheet") { showSheet = true } } .sheet(isPresented: $showSheet) { UpdateCounterView(counter: $counter) } } }
  19. struct ContentView: View { @State private var showSheet = false

    @State private var counter = 0 var body: some View { VStack { Text(counter.description) Button("Show Sheet") { showSheet = true } } .sheet(isPresented: $showSheet) { UpdateCounterView(counter: $counter) } } }
  20. struct UpdateCounterView: View { @Environment(\.presentationMode) var presentationMode @Binding var counter:

    Int var body: some View { NavigationView { VStack { Text(counter.description) Button("Increase counter") { counter += 1 } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Close") { presentationMode.wrappedValue.dismiss() } } } } } }
  21. struct UpdateCounterView: View { @Environment(\.presentationMode) var presentationMode @Binding var counter:

    Int var body: some View { NavigationView { VStack { Text(counter.description) Button("Increase counter") { counter += 1 } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Close") { presentationMode.wrappedValue.dismiss() } } } } } }
  22. struct UpdateCounterView: View { @Environment(\.presentationMode) var presentationMode @Binding var counter:

    Int var body: some View { NavigationView { VStack { Text(counter.description) Button("Increase counter") { counter += 1 } } .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("Close") { presentationMode.wrappedValue.dismiss() } } } } } }
  23. struct ContentView: View { @State var name = "" @State

    var selectedDate = Date() @State var subscribeToNews = false var body: some View { Form { Section(header: Text("Personal info")) { TextField("Name", text: $name) DatePicker("When is your birthday?", selection: $selectedDate, displayedComponents: .date) } Toggle(isOn: $subscribeToNews) { Text("Subscribe to news") } } } }
  24. let name = "Uwe" var body: some View { Text("Hello,

    \(name)!") } "Hello, %@!" = "Hallo, %@!";
  25. private let itemsCount = 5 var body: some View {

    Text("\(itemsCount) items") .font(.largeTitle) } "%lld items" = "%lld Artikel";
  26. struct CustomButtonModifier: ViewModifier { func body(content: Content) -> some View

    { content .font(.title) .foregroundColor(.white) .padding() .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 20)) } } extension View { func customButton() -> some View { modifier(CustomButtonModifier()) } } Button("Continue", action: {})
  27. struct CustomButtonView: View { var title: LocalizedStringKey var body: some

    View { Text(title) .font(.title) .foregroundColor(.white) .padding() .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 20)) } }
  28. struct CusttomButtonStyle: ButtonStyle { func makeBody(configuration: Configuration) -> some View

    { configuration.label .font(.title) .foregroundColor(.white) .padding() .background(Color.blue) .clipShape(RoundedRectangle(cornerRadius: 20)) } } Button("Continue", action: {})
  29. - Moves very fast UIKit can be a saviour Hard

    to do something out of bounds Navigation is very tricky
  30. + Very fast to prototype ideas Need to shift the

    mindset Comes with many niceties Developers like shiny new stu ff :)
  31. SwiftUI app gotchas Prototype in minutes Hard to integrate 3rd

    party libs Even Apple don’t support their own things
  32. Is SwiftUI production ready? If you can stick with latest

    iOS If need to go back to UIKit it can be a lot of hassle I would start a new project with SwifUI