【问题标题】:SwiftUI custom alignment pushes views wider than parentSwiftUI 自定义对齐方式推动视图比父视图更宽
【发布时间】:2023-04-08 20:23:01
【问题描述】:

我从这个开始。一些带有两个滑块的 UI。

由此代码定义:

struct ContentView: View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    var body: some View {
        VStack {
            Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)
            VStack {
                HStack {
                    Text("Value:")
                    Slider(value: $num1, in: 0...1)
                }
                HStack {
                    Text("Opacity:")
                    Slider(value: $num2, in: 0...1)
                }
            }
            Spacer()
        }.border(Color.green, width: 1.0).padding()
    }
}

我希望“值:”和“不透明度:”标签在其后沿对齐,使滑块对齐且宽度相同。我听说自定义对齐可以对齐这样的视图,不是兄弟的视图。

所以我添加了自定义对齐方式:

extension HorizontalAlignment {
    private enum MyAlignment : AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.trailing]
        }
    }
    static let myAlignment = HorizontalAlignment(MyAlignment.self)
}

struct ContentView: View {
    @State private var num1: Double = 0.5
    @State private var num2: Double = 0.5
    let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    var body: some View {
        VStack {
            Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)
            VStack(alignment: .myAlignment) {
                HStack {
                    Text("Value:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                    Slider(value: $num1, in: 0...1)
                }
                HStack {
                    Text("Opacity:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                    Slider(value: $num2, in: 0...1)
                }
            }
            Spacer()
        }.border(Color.green, width: 1.0).padding()
    }
}

它部分有效。标签的后沿已对齐,但现在 UI 已部分移出屏幕右侧。

使用 UIKit 和自动布局,我会将这两个标签的后沿限制为相等。这不会将任何东西推离屏幕(假设事情像往常一样被限制在屏幕边缘)。这里出了什么问题?以及如何通过对齐或更好的方式使用 SwiftUI 来实现这一点?

【问题讨论】:

    标签: alignment swiftui


    【解决方案1】:

    一种方法是使用首选项(了解更多here)。

    struct ContentView: View {
        @State private var num1: Double = 0.5
        @State private var num2: Double = 0.5
        @State private var sliderWidth: CGFloat = 0
    
        private let spacing: CGFloat = 5
        let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
    
        var body: some View {
            VStack {
                Circle().fill(blueGreen).border(Color.blue, width: 1.0).padding(4.0)
    
                VStack(alignment: .trailing) {
                    HStack(spacing: self.spacing) {
                        Text("Value:")
                            .fixedSize()
                            .anchorPreference(key: MyPrefKey.self, value: .bounds, transform: { [$0] })
    
                        Slider(value: $num1, in: 0...1)
                            .frame(width: sliderWidth)
                    }
                    .frame(maxWidth: .infinity, alignment: .trailing)
    
    
                    HStack(spacing: self.spacing) {
                        Text("Opacity:")
                            .fixedSize()
                            .anchorPreference(key: MyPrefKey.self, value: .bounds, transform: { [$0] })
    
                        Slider(value: $num2, in: 0...1)
                            .frame(width: sliderWidth)
                    }
                    .frame(maxWidth: .infinity, alignment: .trailing)
    
                }
                .backgroundPreferenceValue(MyPrefKey.self) { prefs -> GeometryReader<AnyView> in
    
                   GeometryReader { proxy -> AnyView in
                        let vStackWidth = proxy.size.width
    
                        let maxAnchor = prefs.max {
                            return proxy[$0].size.width < proxy[$1].size.width
    
                        }
    
                        DispatchQueue.main.async {
                            if let a = maxAnchor {
                                self.sliderWidth = vStackWidth - (proxy[a].size.width + self.spacing)
    
                            }
                        }
    
                        return AnyView(EmptyView())
    
                    }
                }
    
                Spacer()
    
            }.border(Color.green, width: 1.0).padding(20)
        }
    
    }
    
    struct MyPrefKey: PreferenceKey {
        typealias Value = [Anchor<CGRect>]
    
        static var defaultValue: [Anchor<CGRect>] = []
    
        static func reduce(value: inout [Anchor<CGRect>], nextValue: () -> [Anchor<CGRect>]) {
            value.append(contentsOf: nextValue())
        }
    }
    

    【讨论】:

    • 谢谢!我对此进行了一些调整以设置标签宽度并将滑块排除在外,并将.backgroundPreferenceValue 块隐藏在可重复使用的.setWidth(MyKey.self, $widthBinding) 修饰符后面。我怀疑这不是他们想要使用“首选项”的方式,但您已经找到了一种解决 SwiftUI 当前缺点的好方法。
    • 我选择设置滑块的宽度,因为根据标签的宽度设置标签的宽度可能会带来麻烦。如果 SwiftUI 将来更改它调用闭包和刷新视图的顺序,如果这会产生某种循环,我不会感到惊讶。
    • @kontiki 这可以通过将 PreferenceKey.Value 定义为字典来解决,其中键为每个视图(又名标签)唯一分配
    【解决方案2】:

    好吧,我同意 Apple 似乎忘记在 SwiftUI 中使用/来自自动布局引擎给我们一些东西,比如内容拥抱优先级、抗压缩、大小相等等......

    在 SwiftUI 中对齐指南不会更改视图大小,它们只是“按原样”移动视图以对齐到指定的锚点(如果容器的大小不受限制,则扩展容器,因为默认情况下它会紧贴内容,这就是您所看到的) .因此,要解决考虑的情况,需要以某种方式限制(或更改)大小。

    下面的代码不是完整的解决方案(它没有考虑可能的l10n),但显示了方向 - 因为Slider没有默认大小,应该限制一些如何不扩展容器。

    无论如何,我会考虑以下作为解决方法...(宽度的计算可能更准确和复杂,例如获取文本框架,例如通过锚首选项,然后考虑到填充从可用几何代理宽度中减去,但这仍然是相同的 - 明确指定视图框架宽度)。

    struct HueSaturationView : View {
        @State private var num1: Double = 0.5
        @State private var num2: Double = 0.5
        let blueGreen = Color(red: 0.2, green: 0.6, blue: 0.6)
        var body: some View {
            GeometryReader { gp in
                VStack {
                    Circle().fill(self.blueGreen).border(Color.blue, width: 1.0).padding(4.0)
                    VStack(alignment: .myAlignment) {
                        HStack {
                            Text("Value:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                            Slider(value: self.$num1, in: 0...1)
                                .frame(width: gp.size.width * 2/3)
                        }
                        HStack {
                            Text("Opacity:").alignmentGuide(.myAlignment) { d in d[.trailing] }
                            Slider(value: self.$num2, in: 0...1)
                                .frame(width: gp.size.width * 2/3)
                        }
                    }
                    Spacer()
                }
                .border(Color.green, width: 1.0).padding()
            }
        }
    }
    

    【讨论】:

    • 感谢您的回答。您说:“在 SwiftUI 对齐指南中,它们不会更改视图大小,它们只是按原样移动视图以对齐到指定的锚点”。您是否在文档或 WWDC 谈话中找到了这个?或者你是基于你在使用 SwiftUI 时观察到的?因为从理论上讲,它可以满足对齐方式,就像约束一样,然后然后计算(或重新计算)视图大小。很难知道他们什么时候没有记录任何东西!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-26
    • 2021-03-30
    • 2020-03-09
    • 1970-01-01
    • 2012-05-22
    • 1970-01-01
    相关资源
    最近更新 更多