我正在尝试重新创建Twitter iOS应用的一部分以学习SwiftUI,并且想知道如何动态地将一个视图的宽度更改为另一视图的宽度。就我而言,使下划线与“文本”视图的宽度相同。
我已附上屏幕截图,以尝试更好地解释我的意思。任何帮助将不胜感激,谢谢!
这也是我到目前为止的代码:
import SwiftUI
struct GridViewHeader : View {
@State var leftPadding: Length = 0.0
@State var underLineWidth: Length = 100
var body: some View {
return VStack {
HStack {
Text("Tweets")
.tapAction {
self.leftPadding = 0
}
Spacer()
Text("Tweets & Replies")
.tapAction {
self.leftPadding = 100
}
Spacer()
Text("Media")
.tapAction {
self.leftPadding = 200
}
Spacer()
Text("Likes")
}
.frame(height: 50)
.padding(.horizontal, 10)
HStack {
Rectangle()
.frame(width: self.underLineWidth, height: 2, alignment: .bottom)
.padding(.leading, leftPadding)
.animation(.basic())
Spacer()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
And*_*ndy 28
首先,回答标题中的问题,如果你想让一个形状(视图)适合另一个视图的大小,你可以使用.overlay(). 该.overlay()会从它被修改视图提供它的大小。
为了在您的 Twitter 娱乐中设置偏移量和宽度,您可以使用GeometryReader. 在GeometryReader必须找到它的能力,.frame(in:)另一个坐标空间。
您可以使用.coordinateSpace(name:)来标识参考坐标空间。
struct ContentView: View {
@State private var offset: CGFloat = 0
@State private var width: CGFloat = 0
var body: some View {
HStack {
Text("Tweets")
.overlay(MoveUnderlineButton(offset: $offset, width: $width))
Text("Tweets & Replies")
.overlay(MoveUnderlineButton(offset: $offset, width: $width))
Text("Media")
.overlay(MoveUnderlineButton(offset: $offset, width: $width))
Text("Likes")
.overlay(MoveUnderlineButton(offset: $offset, width: $width))
}
.coordinateSpace(name: "container")
.overlay(underline, alignment: .bottomLeading)
.animation(.spring())
}
var underline: some View {
Rectangle()
.frame(height: 2)
.frame(width: width)
.padding(.leading, offset)
}
struct MoveUnderlineButton: View {
@Binding var offset: CGFloat
@Binding var width: CGFloat
var body: some View {
GeometryReader { geometry in
Button(action: {
self.offset = geometry.frame(in: .named("container")).minX
self.width = geometry.size.width
}) {
Rectangle().foregroundColor(.clear)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
underline视图是有2点高Rectangle,放于一个.overlay()上的顶部HStack。underline视图对齐.bottomLeading,这样我们就可以将其编程设置.padding(.leading, _)使用@State值。.frame(width:)也是使用@State值设置的。HStack设置为.coordinateSpace(name: "container"),所以我们可以找到我们的按钮相对于该框架。MoveUnderlineButton使用GeometryReader中找到了自己width,并minX以设置相应的值的underline视图MoveUnderlineButton被设置为.overlay()用于Text包含该按钮的文本,以便其视图GeometryReader继承其大小从Text图。 kon*_*iki 16
我已经编写了有关使用GeometryReader,视图首选项和锚点首选项的详细说明。下面的代码使用了这些概念。有关它们如何工作的更多信息,请查看我发表的这篇文章:https : //swiftui-lab.com/communication-with-the-view-tree-part-1/
以下解决方案将为下划线正确设置动画:
我努力做到这一点,我同意你的看法。有时,您只需要能够向上或向下传递层次结构以及一些框架信息。实际上,WWDC2019会议237(使用SwiftUI构建自定义视图)说明了视图会不断传达其大小。它基本上说父母建议孩子的大小,孩子决定他们要如何布局自己并与父母沟通。他们是如何做到的?我怀疑anchorPreference与它有关。但是,它非常晦涩,尚未完全记录在案。该API公开了,但是掌握了这些长函数原型的工作原理……这真是太糟糕了,我现在没有时间。
我认为Apple对此文件未作记录,迫使我们重新考虑整个框架,忘掉“旧的” UIKit习惯并开始声明式思考。但是,有时仍然需要这样做。您是否想知道背景修饰符如何工作?我很乐意看到该实现。这会解释很多!我希望苹果公司能在不久的将来记录下偏好设置。我一直在尝试自定义PreferenceKey,它看起来很有趣。
现在回到您的特定需求,我设法解决了。您需要两个尺寸(文本的x位置和宽度)。一个我公平公正,另一个似乎有点不合时宜。但是,它运行良好。
我通过创建自定义水平对齐方式解决了文本的x位置。有关该检查会话237的更多信息(在19:00分钟)。尽管我建议您仔细观察整个过程,但它可以使您对布局过程的工作方式有更多的了解。
宽度,但是,我不是很自豪... ;-)它需要DispatchQueue避免在显示时更新视图。更新:我在下面的第二个实现中修复了它
import SwiftUI
extension HorizontalAlignment {
private enum UnderlineLeading: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.leading]
}
}
static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}
struct GridViewHeader : View {
@State private var activeIdx: Int = 0
@State private var w: [Length] = [0, 0, 0, 0]
var body: some View {
return VStack(alignment: .underlineLeading) {
HStack {
Text("Tweets").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 0))
Spacer()
Text("Tweets & Replies").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 1))
Spacer()
Text("Media").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 2))
Spacer()
Text("Likes").modifier(MagicStuff(activeIdx: $activeIdx, widths: $w, idx: 3))
}
.frame(height: 50)
.padding(.horizontal, 10)
Rectangle()
.alignmentGuide(.underlineLeading) { d in d[.leading] }
.frame(width: w[activeIdx], height: 2)
.animation(.basic())
}
}
}
struct MagicStuff: ViewModifier {
@Binding var activeIdx: Int
@Binding var widths: [Length]
let idx: Int
func body(content: Content) -> some View {
Group {
if activeIdx == idx {
content.alignmentGuide(.underlineLeading) { d in
DispatchQueue.main.async { self.widths[self.idx] = d.width }
return d[.leading]
}.tapAction { self.activeIdx = self.idx }
} else {
content.tapAction { self.activeIdx = self.idx }
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我的第一个解决方案可行,但是我对宽度传递给下划线视图的方式并不感到骄傲。
我找到了实现同一目标的更好方法。事实证明,背景修饰符非常强大。它不只是可以让您装饰视图背景的修饰符。
基本步骤是:
Text("text").background(TextGeometry())。TextGeometry是一个自定义视图,其父视图的大小与文本视图相同。那就是.background()所做的。很强大。听起来似乎太复杂了,但是代码很好地说明了这一点。这是新的实现:
import SwiftUI
extension HorizontalAlignment {
private enum UnderlineLeading: AlignmentID {
static func defaultValue(in d: ViewDimensions) -> Length {
return d[.leading]
}
}
static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}
struct WidthPreferenceKey: PreferenceKey {
static var defaultValue = CGFloat(0)
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
typealias Value = CGFloat
}
struct GridViewHeader : View {
@State private var activeIdx: Int = 0
@State private var w: [Length] = [0, 0, 0, 0]
var body: some View {
return VStack(alignment: .underlineLeading) {
HStack {
Text("Tweets")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 0))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[0] = $0 })
Spacer()
Text("Tweets & Replies")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 1))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[1] = $0 })
Spacer()
Text("Media")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 2))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[2] = $0 })
Spacer()
Text("Likes")
.modifier(MagicStuff(activeIdx: $activeIdx, idx: 3))
.background(TextGeometry())
.onPreferenceChange(WidthPreferenceKey.self, perform: { self.w[3] = $0 })
}
.frame(height: 50)
.padding(.horizontal, 10)
Rectangle()
.alignmentGuide(.underlineLeading) { d in d[.leading] }
.frame(width: w[activeIdx], height: 2)
.animation(.basic())
}
}
}
struct TextGeometry: View {
var body: some View {
GeometryReader { geometry in
return Rectangle().preference(key: WidthPreferenceKey.self, value: geometry.size.width)
}
}
}
struct MagicStuff: ViewModifier {
@Binding var activeIdx: Int
let idx: Int
func body(content: Content) -> some View {
Group {
if activeIdx == idx {
content.alignmentGuide(.underlineLeading) { d in
return d[.leading]
}.tapAction { self.activeIdx = self.idx }
} else {
content.tapAction { self.activeIdx = self.idx }
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5298 次 |
| 最近记录: |