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 ↗Code shown on screen · 10 snippets
Slide 13 - Textfield and Securefield
import SwiftUI
import AuthenticationServices
struct ContentView: View {
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
private var password: String = ""
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
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
private var password: String = ""
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
import SwiftUI
import AuthenticationServices
enum Field: Hashable {
case email
case password
}
struct ContentView: View {
private var focusedField: Field?
private var email: String = ""
private var password: String = ""
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
import SwiftUI
import AuthenticationServices
struct ContentView: View {
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
struct ContentView: View {
private var email: String = ""
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
import SwiftUI
import AuthenticationServices
struct ContentView: View {
private var email: String = ""
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
Related sessions
-
19 min -
40 min -
17 min