You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:05:01