SwiftUI中如何修改父视图内单个子视图的状态与文本?
问题描述
- 开发的LetterBox组件由圆角矩形、隐藏TextField和显示最后一个字符的Text视图组成,按目标单词长度排成一行
- 需求:点击单个LetterBox时,组件变色,且对应TextField和Text内容可修改,但目前文本修改的目标并非选中组件,结果不可预测
- 同时遇到错误提示(翻译后):
错误:此应用程序或其使用的库向CoreGraphics API传递了无效数值(NaN,即非数字),该数值已被忽略。请修复此问题。
LetterBox 组件代码
import SwiftUI struct LetterBox: View { @State var letter: String var status: LetterStatus var boxColor: Color { switch status { case .changing: return Color(.metallicSeaweed) case .notChanging: return Color(.platinum) case .acceptable: return Color(.yellowGreenCrayola) case .notAcceptable: return Color(.rubyRed) } } var body: some View { ZStack { RoundedRectangle(cornerRadius: 4) .style(withStroke: Color.black, lineWidth: 1, fill: boxColor) .aspectRatio(1, contentMode: .fit) TextField("", text: $letter) .font(.custom("Lovely Madness", size: 55)) .multilineTextAlignment(.center) .tint(Color.clear) .foregroundColor(Color.clear) if letter.isEmpty { Text(" ") .font(.custom("Lovely Madness", size: 55)) .multilineTextAlignment(.center) } else { Text(String(letter.last!)) .font(.custom("Lovely Madness", size: 55)) .multilineTextAlignment(.center) } } } } enum LetterStatus { case changing case notChanging case acceptable case notAcceptable } extension Shape { func style<StrokeStyle: ShapeStyle, FillStyle: ShapeStyle>( withStroke strokeContent: StrokeStyle, lineWidth: CGFloat = 1, fill fillContent: FillStyle ) -> some View { stroke(strokeContent, lineWidth: lineWidth) .background(fill(fillContent)) } } struct LetterBox_Previews: PreviewProvider { static var previews: some View { LetterBox(letter: .init("J"), status: .notChanging) .previewLayout(.fixed(width: 100, height: 100)) LetterBox(letter: .init("A"), status: .changing) .previewLayout(.fixed(width: 100, height: 100)) } }
LetterBoxRowFunctional 组件代码
import SwiftUI struct LetterBoxRowFunctional: View { var word: String var row: [GridItem] { [GridItem(.fixed(CGFloat(word.count)))] } var boxSize: CGFloat { switch word.count { case 6: return 60 default: return 75 } } @State var letterBoxArray: [LetterBox] @State var selected: Int = -1 @State var userEntry: String = "" @FocusState var keyboardFocused: Bool var body: some View { VStack { LazyHGrid(rows: row) { ForEach (0...word.count - 1, id: \.self) { num in letterBoxArray[num] .frame(width: boxSize, height: boxSize) .onTapGesture { changeStatusToChanging(index: num, len: word.count, array: &letterBoxArray) selected = num print(selected) } } } TextField("", text: $userEntry) .keyboardType(.alphabet) .foregroundStyle(Color.clear) .tint(Color.clear) .focused($keyboardFocused) .autocorrectionDisabled(true) .onChange(of: userEntry.count | selected ) { if selected >= 0 { letterBoxArray[selected].letter = String(userEntry.suffix(1)) } } }.onAppear(perform: {keyboardFocused = true}) } init(word: String) { self.word = word self.letterBoxArray = getLetterBoxArray(word: word) } } func getLetterBoxArray(word: String) -> [LetterBox] { var arr: [LetterBox] = [] for char in word { arr += [LetterBox(letter: String(char), status: .notChanging)] } return arr } func changeStatusToChanging(index: Int, len: Int, array: inout [LetterBox]) { for idx in 0...len - 1 { if idx == index { array[idx].status = .changing } else { array[idx].status = .notChanging } } } struct LetterBoxRowFunctionalPreview: PreviewProvider { static var previews: some View { LetterBoxRowFunctional(word: "love") } }
修复方案
1. 文本修改目标错误问题
核心问题是直接存储[LetterBox]数组无法正确管理组件状态——LetterBox的@State是内部状态,外部修改不会触发正确的视图更新。需要重构状态管理逻辑:
修改LetterBox组件
把内部状态改为@Binding,让父组件统一控制数据:
struct LetterBox: View { @Binding var letter: String @Binding var status: LetterStatus var boxColor: Color { switch status { case .changing: return Color(.metallicSeaweed) case .notChanging: return Color(.platinum) case .acceptable: return Color(.yellowGreenCrayola) case .notAcceptable: return Color(.rubyRed) } } var body: some View { ZStack { RoundedRectangle(cornerRadius: 4) .style(withStroke: Color.black, lineWidth: 1, fill: boxColor) .aspectRatio(1, contentMode: .fit) TextField("", text: $letter) .font(.custom("Lovely Madness", size: 55)) .multilineTextAlignment(.center) .tint(Color.clear) .foregroundColor(Color.clear) Text(letter.isEmpty ? " " : String(letter.last!)) .font(.custom("Lovely Madness", size: 55)) .multilineTextAlignment(.center) } } }
修改LetterBoxRowFunctional组件
用数据模型存储每个字母的状态和文本,而非直接存储组件实例:
struct LetterBoxRowFunctional: View { struct LetterData: Identifiable { let id = UUID() var letter: String var status: LetterStatus } var word: String var row: [GridItem] { Array(repeating: GridItem(.flexible()), count: word.count) } var boxSize: CGFloat { switch word.count { case 6: return 60 default: return 75 } } @State private var letterDataArray: [LetterData] @State private var selected: Int = -1 @State private var userEntry: String = "" @FocusState private var keyboardFocused: Bool var body: some View { VStack { LazyHGrid(rows: row) { ForEach($letterDataArray) { $data in let index = letterDataArray.firstIndex(where: { $0.id == data.id }) ?? -1 LetterBox(letter: $data.letter, status: $data.status) .frame(width: boxSize, height: boxSize) .onTapGesture { selected = index updateStatuses(selectedIndex: index) } } } TextField("", text: $userEntry) .keyboardType(.alphabet) .foregroundStyle(Color.clear) .tint(Color.clear) .focused($keyboardFocused) .autocorrectionDisabled(true) .onChange(of: userEntry) { newValue in guard selected >= 0, selected < letterDataArray.count else { return } letterDataArray[selected].letter = String(newValue.suffix(1)) } .onChange(of: selected) { _ in guard selected >= 0, selected < letterDataArray.count else { return } userEntry = letterDataArray[selected].letter } } .onAppear { keyboardFocused = true } } init(word: String) { self.word = word self._letterDataArray = State(initialValue: word.map { LetterData(letter: String($0), status: .notChanging) }) } private func updateStatuses(selectedIndex: Int) { for idx in letterDataArray.indices { letterDataArray[idx].status = idx == selectedIndex ? .changing : .notChanging } } }
2. CoreGraphics NaN错误问题
错误来自LazyHGrid的网格项设置:原代码[GridItem(.fixed(CGFloat(word.count)))]将网格宽度设为单词长度(比如"love"对应4),导致布局计算出现无效数值。改为Array(repeating: GridItem(.flexible()), count: word.count),让每个网格项自适应空间,即可解决该错误。
内容的提问来源于stack exchange,提问作者user24683848
相关产品推荐
相关产品推荐

