iOS17下SwiftUI登录界面输入邮箱时登录按钮抖动问题
问题解决:iOS17下SwiftUI登录按钮随邮箱输入抖动
问题现象
用SwiftUI开发的登录界面包含邮箱输入框、密码输入框和底部登录按钮,iOS16环境运行正常,但升级到iOS17后,在邮箱输入框输入字符时底部登录按钮会出现上下抖动;密码输入框输入时无此问题,移除密码输入框后抖动现象消失。
问题原因
iOS17的SwiftUI布局引擎对多输入控件(带@FocusState)的重计算逻辑做了调整,加上当前布局中底部按钮依赖Spacer()自适应位置,当上方输入框因文本输入、焦点变化触发微小布局偏移时,会传导到底部按钮引发抖动。此外,每个CustomTextField独立维护@FocusState,进一步加剧了布局重绘的频率。
修复方案
方案1:固定底部按钮布局位置
将底部按钮从依赖Spacer()的自适应布局改为固定底部约束,避免上方视图变化影响其位置:
struct LogInView: View { @State private var email = "" @State private var password = "" var body: some View { ZStack(alignment: .top){ VStack { VStack(spacing: 15){ CustomTextField(imageName: "envelope", placeHolderText: "Email", text: $email) CustomTextField(imageName: "lock", placeHolderText: "Password", isSecureField: true, text: $password) }.padding(.horizontal, 32) }.padding(.top, 20) // 调整底部按钮布局,固定底部内边距 VStack { Spacer() Button { // 登录逻辑 } label: { Text("Sign in") .font(.headline) .foregroundColor(.white) .frame(width: 300, height: 50) .background(Color.systemOrange.opacity(0.7)) .clipShape(Capsule()) } .padding(.bottom, 20) } } .ignoresSafeArea(edges: .top) // 仅让顶部忽略安全区,避免影响底部布局 } }
方案2:统一管理输入框焦点状态
将@FocusState提升到父视图统一管理,减少子视图独立焦点状态导致的频繁重绘:
struct LogInView: View { @State private var email = "" @State private var password = "" @FocusState private var focusedField: FocusedField? enum FocusedField { case email, password } var body: some View { ZStack(alignment: .top){ VStack { VStack(spacing: 15){ CustomTextField( imageName: "envelope", placeHolderText: "Email", text: $email, isSecureField: false, focusedField: $focusedField, field: .email ) CustomTextField( imageName: "lock", placeHolderText: "Password", isSecureField: true, text: $password, focusedField: $focusedField, field: .password ) }.padding(.horizontal, 32) }.padding(.top, 20) VStack { Spacer() Button { // 登录逻辑 } label: { Text("Sign in") .font(.headline) .foregroundColor(.white) .frame(width: 300, height: 50) .background(Color.systemOrange.opacity(0.7)) .clipShape(Capsule()) } .padding(.bottom, 20) } } .ignoresSafeArea(edges: .top) } } struct CustomTextField: View { let imageName: String let placeHolderText: String let isSecureField: Bool @Binding var text: String @Environment(\.colorScheme) var colorScheme @Binding var focusedField: LogInView.FocusedField? let field: LogInView.FocusedField var body: some View { HStack{ Image(systemName: imageName) .resizable() .scaledToFit() .frame(width: 20, height: 20) .foregroundColor(colorScheme == .dark ? .white : .gray) if isSecureField { SecureField(placeHolderText, text: $text) .tint(colorScheme == .dark ? .white : .black) .focused($focusedField, equals: field) } else { TextField(placeHolderText, text: $text) .tint(colorScheme == .dark ? .white : .black) .focused($focusedField, equals: field) } } .padding(.vertical, 12) .padding(.horizontal, 5) .overlay(RoundedRectangle(cornerRadius: 5).stroke(Color.gray, lineWidth: focusedField == field ? 2 : 1)) } }
方案3:提升按钮布局稳定性
给按钮添加.layoutPriority(1)提升布局优先级,或用.fixedSize()固定尺寸,减少抖动:
Button { // 登录逻辑 } label: { Text("Sign in") .font(.headline) .foregroundColor(.white) .frame(width: 300, height: 50) .background(Color.systemOrange.opacity(0.7)) .clipShape(Capsule()) } .layoutPriority(1) // 提升布局优先级,优先确定按钮位置 .padding()
补充说明
原代码中background(.systemOrange).opacity(0.7))存在语法错误(多余一个右括号),以上修复方案已修正该问题,避免编译报错。
内容的提问来源于stack exchange,提问作者Ahmed Zaidan
相关产品推荐
相关产品推荐

