Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2021 SwiftUI & UI Frameworks

WWDC21 · 11 min · SwiftUI & UI Frameworks

Direct and reflect focus in SwiftUI

With device input — as with all things in life — where you put focus matters. Discover how you can move focus in your app with SwiftUI, programmatically dismiss the keyboard, and build large navigation targets from small views. Together, these APIs can help you simplify your app’s interface and make it more powerful for people to find what they need.

Watch at developer.apple.com ↗

Transcript all transcripts

Code shown on screen · 10 snippets

Slide 13 - Textfield and Securefield swift · at 3:38 ↗
import SwiftUI
import AuthenticationServices

struct ContentView: View {

    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
        }
    }

}
Slide 14 - Focus State swift · at 3:49 ↗
import SwiftUI
import AuthenticationServices

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
        }
    }

}
Slide 15 - Focus Field swift · at 4:07 ↗
import SwiftUI
import AuthenticationServices

enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
        }
    }

}
Slide 17 - focused modifiers swift · at 4:32 ↗
import SwiftUI
import AuthenticationServices

enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .email)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .password)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
        }
    }

}
Slide 25 - onSubmit swift · at 6:07 ↗
import SwiftUI
import AuthenticationServices

enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var submittedEmail: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .email)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .password)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
            .onSubmit {
                submittedEmail = email
                if !isEmailValid {
                    focusedField = .email
                }
            }
        }
    }
  
    private var isEmailValid : Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
        return submittedEmail.isEmpty || predicate.evaluate(with: submittedEmail)
    }

}
Slide 26 - border swift · at 6:25 ↗
import SwiftUI
import AuthenticationServices

enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var submittedEmail: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .email)
                    .border(Color.red,
                            width: (focusedField == .email &&
                                    !isEmailValid) ? 2 : 0)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .password)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
            .onSubmit {
                submittedEmail = email
                if !isEmailValid {
                    focusedField = .email
                }
            }
        }
    }
  
    private var isEmailValid : Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
        return submittedEmail.isEmpty || predicate.evaluate(with: submittedEmail)
    }

}
Slide 29 - dismiss keyboard with nil swift · at 7:17 ↗
import SwiftUI
import AuthenticationServices

enum Field: Hashable {
    case email
    case password
}

struct ContentView: View {

    @FocusState private var focusedField: Field?
    @State private var email: String = ""
    @State private var password: String = ""
    @State private var submittedEmail: String = ""

    var body: some View {
        ZStack {
            Image("backgroundImage")
                .resizable()
                .opacity(0.7)
                .ignoresSafeArea()

            VStack(alignment: .center) {
                Text("Vacation Planner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(.black.opacity(0.8))
                    .frame(alignment: .top)

                Spacer(minLength: 30)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)
                    .padding()
                    .frame(height: 50)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .email)
                    .border(Color.red,
                            width: (focusedField == .email &&
                                    !isEmailValid) ? 2 : 0)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .padding()
                    .frame(height:50)
                    .textContentType(.password)
                    .background(Color.white.opacity(0.9))
                    .cornerRadius(15)
                    .padding(10)
                    .focused($focusedField, equals: .password)

                Spacer().frame(height: 20)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(.black.opacity(0.7))
                
                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                .cornerRadius(15)

                Spacer().frame(height: 20)

            }
            .frame(width: 280, height: 500, alignment: .bottom)
            .onSubmit {
                submittedEmail = email
                if !isEmailValid {
                    focusedField = .email
                } else {
                    focusedField = nil
                    // Show progress indicator, and log in.
                }
            }
        }
    }
  
    private var isEmailValid : Bool {
        let regex = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}"
        let predicate = NSPredicate(format:"SELF MATCHES %@", regex)
        return submittedEmail.isEmpty || predicate.evaluate(with: submittedEmail)
    }

}
tv code swift · at 9:24 ↗
import SwiftUI
import AuthenticationServices


struct ContentView: View {

    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Spacer(minLength:60).frame(height: 150)
                Text("Vacation\nPlanner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(Color.black.opacity(0.8))
                    .lineLimit(nil)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 40)

                Spacer().frame(height:80)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)

                Spacer().frame(height:30)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .textContentType(.password)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(Color.black.opacity(0.7))

                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                Spacer()
            }
            .frame(width: 350, alignment: .center)

            VStack {
                Image(photoName)
                    .resizable()
                    .frame(width: 1400)
                    .aspectRatio(contentMode: .fit)
                    .ignoresSafeArea(edges: [.trailing])
                BrowsePhotosButton()
            }
        }.preferredColorScheme(.light)
    }
}
focus section 1 swift · at 9:47 ↗
import SwiftUI
import AuthenticationServices


struct ContentView: View {

    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Spacer(minLength:60).frame(height: 150)
                Text("Vacation\nPlanner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(Color.black.opacity(0.8))
                    .lineLimit(nil)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 40)

                Spacer().frame(height:80)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)

                Spacer().frame(height:30)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .textContentType(.password)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(Color.black.opacity(0.7))

                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                Spacer()
            }
            .frame(width: 350, alignment: .center)

            VStack {
                Image(photoName)
                    .resizable()
                    .frame(width: 1400)
                    .aspectRatio(contentMode: .fit)
                    .ignoresSafeArea(edges: [.trailing])
                BrowsePhotosButton()
            }
            .focusSection()
        }.preferredColorScheme(.light)
    }
}
focus section 2 swift · at 10:06 ↗
import SwiftUI
import AuthenticationServices


struct ContentView: View {

    @State private var email: String = ""
    @State private var password: String = ""

    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Spacer(minLength:60).frame(height: 150)
                Text("Vacation\nPlanner")
                    .font(.custom("Baskerville-SemiBoldItalic", size: 60))
                    .foregroundColor(Color.black.opacity(0.8))
                    .lineLimit(nil)
                    .multilineTextAlignment(.center)
                    .padding(.horizontal, 40)

                Spacer().frame(height:80)

                TextField("Email", text: $email)
                    .submitLabel(.next)
                    .textContentType(.emailAddress)
                    .keyboardType(.emailAddress)

                Spacer().frame(height:30)

                SecureField("Password", text: $password)
                    .submitLabel(.go)
                    .textContentType(.password)

                HStack {
                    Rectangle().frame(height: 1)
                    Text("or").bold().padding()
                    Rectangle().frame(height: 1)
                }
                .foregroundColor(Color.black.opacity(0.7))

                Spacer().frame(height: 20)

                SignInWithAppleButton(.signIn) { request in
                    request.requestedScopes = [.fullName, .email]
                } onCompletion: { result in
                    switch result {
                    case .success (_):
                        print("Authorization successful.")
                    case .failure (let error):
                        print("Authorization failed: " + error.localizedDescription)
                    }
                }
                .frame(height: 50)
                Spacer()
            }
            .frame(width: 350, alignment: .center)
            .focusSection()

            VStack {
                Image(photoName)
                    .resizable()
                    .frame(width: 1400)
                    .aspectRatio(contentMode: .fit)
                    .ignoresSafeArea(edges: [.trailing])
                BrowsePhotosButton()
            }
            .focusSection()
        }.preferredColorScheme(.light)
    }
}

Resources