SwiftUI 自定义对齐方式推动视图比父视图更宽
Posted
技术标签:
【中文标题】SwiftUI 自定义对齐方式推动视图比父视图更宽【英文标题】:SwiftUI custom alignment pushes views wider than parent 【发布时间】:2020-01-31 21:16:15 【问题描述】:我从这个开始。一些带有两个滑块的 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 来实现这一点?
【问题讨论】:
【参考方案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 时观察到的?因为从理论上讲,它可以满足对齐方式,就像约束一样,然后然后计算(或重新计算)视图大小。很难知道他们什么时候没有记录任何东西!以上是关于SwiftUI 自定义对齐方式推动视图比父视图更宽的主要内容,如果未能解决你的问题,请参考以下文章
如何阻止 SwiftUI 视图变得比 superview 更宽
带有自定义 selectedBackgroundView 的 UITableViewCell,它比 UISplitViewController 上的主视图控制器更宽