SwiftUI - VStack - 输入文本时跳转

SwitUI - VStack - Jumping when text entered

我正在开发一个基本的密码输入屏幕,包括一个显示当前输入的顶部堆栈,然后是一些显示数字的 HStack

    VStack(){
        HStack(spacing: 20){
            ForEach(codes,id: \.self){i in
                Text("*")
            }
        }
        
        HStack(){
            <Number 1 - 3>
        }
        HStack(){
            <Number 4 - 6>
        }
        HStack(){
            <Number 7 - 9>
        }
        HStack(){
            <Number 0>
        }
    }

我面临的这个问题是当没有输入密码时,顶部 HStack 不会用完任何 space,因此垂直高度为 0,当我输入密码时,它会强制整个视图跳转随着视图调整大小。

我怎样才能阻止它

老实说,建造过程非常有趣!如果它解决了您的问题,请不要忘记将此答案标记为正确答案。 ✅

问题

跳跃效果是由于 SwiftUI 根据根据您的内容(密码数字)计算的可用 space 更新所有视图位置。字体、字体粗细、文本大小等...都会影响留给其他视图的可用 space。

解决方案

为避免这种情况,您需要一个预定义的框架,让父视图知道您的数字永远不会占用更多 space。这样做,每次更新都不会影响任何其他视图的位置,因为分配的顶部 space 将始终是您指定的大小,而不是数字大小(或缺失)。

代码

import SwiftUI
import Combine


// Using Combine to manage digits and future network calls…
class PasscodeManager: ObservableObject {
    let codesQuantity = 4
    @Published var codes = [Int]()
}


struct PasscodeView: View {

    @StateObject private var manager = PasscodeManager()

    var body: some View {
        VStack {
            Spacer()
            // Dots placeholders and passcode digits
            selectedCodes
            Spacer()
            // Numberpad
            PasscodeLine(numbers: 1...3) { add(number: [=10=]) }
            PasscodeLine(numbers: 4...6) { add(number: [=10=]) }
            PasscodeLine(numbers: 7...9) { add(number: [=10=]) }
            PasscodeLine(numbers: 0...0) { add(number: [=10=]) }
            Spacer()
        }
        .padding()
    }

    var selectedCodes: some View {
        let minDots = manager.codes.count == manager.codesQuantity ? 0:1
        let maxDots = manager.codesQuantity - manager.codes.count
        return HStack(spacing: 32) {
            ForEach(manager.codes, id: \.self) { Text("\([=10=])") }
            if maxDots != 0 {
                ForEach(minDots...maxDots, id: \.self) { _ in
                    Circle().frame(width: 12)
                }
            }
        }
        .font(.title.bold())
        // Setting a default height should fix your problem.  
        .frame(height: 70)
    }

    func add(number: Int) {
        guard manager.codes.count < manager.codesQuantity else { return }
        manager.codes.append(number)
    }

}

struct PasscodeLine: View {
    let numbers: ClosedRange<Int>
    var select: (Int) -> Void
    var body: some View {
        HStack {
            ForEach(numbers, id: \.self) { number in
                Spacer()
                Button(action: { select(number) },
                       label: {
                        Text("\(number)")
                            .font(.title)
                            .fontWeight(.medium)
                            .foregroundColor(Color(.label))
                            .padding(32)
                            .background(Color(.quaternarySystemFill))
                            .clipShape(Circle())
                       })
            }
            Spacer()
        }
    }
}

结果