如何使视图在SwiftUI中的另一个视图的大小 [英] How to make view the size of another view in SwiftUI

查看:59
本文介绍了如何使视图在SwiftUI中的另一个视图的大小的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试重新创建Twitter iOS应用的一部分以学习SwiftUI,并且想知道如何动态地将一个视图的宽度更改为另一视图的宽度.就我而言,下划线的宽度应与文本"视图的宽度相同.

I'm trying to recreate a portion of the Twitter iOS app to learn SwiftUI and am wondering how to dynamically change the width of one view to be the width of another view. In my case, to have the underline be the same width as the Text view.

我附上了屏幕截图,以尝试更好地解释我所指的内容.任何帮助将不胜感激,谢谢!

I have attached a screenshot to try and better explain what I'm referring to. Any help would be greatly appreciated, thanks!

这也是我到目前为止的代码:

Also here is the code I have so far:

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()
            }
        }
    }
}

推荐答案

我写了关于使用GeometryReader,视图首选项和锚点首选项的详细说明.下面的代码使用了这些概念.有关它们如何工作的更多信息,请查看我发表的文章: https://swiftui-lab.com/communication-with-the-view-tree-part-1/

以下解决方案将为下划线正确设置动画:

The solution below, will properly animate the underline:

我努力做到这一点,我同意你的看法.有时,您只需要能够向上或向下传递层次结构以及一些框架信息.实际上,WWDC2019会议237(使用SwiftUI构建自定义视图)说明了视图会不断传达其大小.它基本上说父母建议孩子的大小,孩子决定他们要如何布局自己并与父母沟通.他们是如何做到的?我怀疑anchorPreference与它有关.但是,它非常晦涩,尚未完全记录在案.该API公开了,但是掌握了这些长函数原型的工作原理……这真是太糟糕了,我现在没有时间.

I struggled to make this work and I agree with you. Sometimes, you just need to be able to pass up or down the hierarchy, some framing information. In fact, the WWDC2019 session 237 (Building Custom Views with SwiftUI), explains that views communicate their sizing continuously. It basically says Parent proposes size to child, childen decide how they want to layout theirselves and communicate back to the parent. How they do that? I suspect the anchorPreference has something to do with it. However it is very obscure and not at all documented yet. The API is exposed, but grasping how those long function prototypes work... that's a hell I do not have time for right now.

我认为Apple对此文件未作记录,迫使我们重新考虑整个框架,而忘记了旧的" UIKit习惯,并开始进行声明式思考.但是,有时仍然需要这样做.您是否想知道背景修饰符如何工作?我很乐意看到该实现.这会解释很多!我希望苹果公司能在不久的将来记录下偏好设置.我一直在尝试使用自定义PreferenceKey,它看起来很有趣.

I think Apple has left this undocumented to force us rethink the whole framework and forget about "old" UIKit habits and start thinking declaratively. However, there are still times when this is needed. Have you ever wonder how the background modifier works? I would love to see that implementation. It would explain a lot! I'm hoping Apple will document preferences in the near future. I have been experimenting with custom PreferenceKey and it looks interesting.

现在回到您的特定需求,我设法解决了.您需要两个尺寸(文本的x位置和宽度).一个我公平公正,另一个似乎有点不合时宜.但是,它运行良好.

Now back to your specific need, I managed to work it out. There are two dimensions you need (the x position and width of the text). One I get it fair and square, the other seems a bit of a hack. Nevertheless, it works perfectly.

我通过创建自定义水平对齐方式解决了文本的x位置.关于该检查会话237的更多信息(在19:00分钟).尽管我建议您仔细阅读整个内容,但它可以为您说明布局过程的工作原理.

The x position of the text I solved it by creating a custom horizontal alignment. More information on that check session 237 (at minute 19:00). Although I recommend you watch the whole thing, it sheds a lot of light on how the layout process works.

宽度,但是,我并不为此感到骄傲... ;-)它需要DispatchQueue避免在显示时更新视图. 更新:我在下面的第二个实现中对其进行了修复

The width, however, I'm not so proud of... ;-) It requires DispatchQueue to avoid updating the view while being displayed. UPDATE: I fixed it in the second implementation down below

extension HorizontalAlignment {
    private enum UnderlineLeading: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            return d[.leading]
        }
    }

    static let underlineLeading = HorizontalAlignment(UnderlineLeading.self)
}


struct GridViewHeader : View {

    @State private var activeIdx: Int = 0
    @State private var w: [CGFloat] = [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(.linear)
        }
    }
}

struct MagicStuff: ViewModifier {
    @Binding var activeIdx: Int
    @Binding var widths: [CGFloat]
    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]
                }.onTapGesture { self.activeIdx = self.idx }

            } else {
                content.onTapGesture { self.activeIdx = self.idx }
            }
        }
    }
}

更新:无需使用DispatchQueue即可实现更好的实现

我的第一个解决方案有效,但是我对宽度传递给下划线视图的方式并不感到骄傲.

Update: Better implementation without using DispatchQueue

My first solution works, but I was not too proud of the way the width is passed to the underline view.

我找到了实现同一目标的更好方法.事实证明,背景修饰符非常强大.它不只是一个修饰符,它可以让您装饰视图的背景.

I found a better way of achieving the same thing. It turns out, the background modifier is very powerful. It is much more than a modifier that can let you decorate the background of a view.

基本步骤是:

  1. 使用Text("text").background(TextGeometry()). TextGeometry是一个自定义视图,其父级的大小与文本视图的大小相同.那就是.background()所做的.非常强大.
  2. 在实现 TextGeometry 的过程中,我使用GeometryReader来获取父级的几何,这意味着,我将获得Text视图的几何,这意味着我现在具有宽度.
  3. 现在要将宽度传回,我正在使用首选项.关于它们的文档为零,但是经过一些试验,我认为如果您愿意,首选项类似于视图属性".我创建了名为 WidthPreferenceKey 的自定义 PreferenceKey ,并在TextGeometry中使用它来将宽度附加"到视图,以便可以在层次结构中更高的位置阅读.
  4. 回到祖先,我使用 onPreferenceChange 来检测宽度的变化,并相应地设置widths数组.
  1. Use Text("text").background(TextGeometry()). TextGeometry is a custom view that has a parent with the same size as the text view. That is what .background() does. Very powerful.
  2. In my implementation of TextGeometry I use GeometryReader, to get the geometry of the parent, which means, I get the geometry of the Text view, which means I now have the width.
  3. Now to pass the width back, I am using Preferences. There's zero documentation about them, but after a little experimentation, I think preferences are something like "view attributes" if you like. I created my custom PreferenceKey, called WidthPreferenceKey and I use it in TextGeometry to "attach" the width to the view, so it can be read higher in the hierarchy.
  4. Back in the ancestor, I use onPreferenceChange to detect changes in the width, and set the widths array accordingly.

听起来可能太复杂了,但是代码很好地说明了这一点.这是新的实现:

It may all sound too complex, but the code illustrates it best. Here's the new implementation:

import SwiftUI

extension HorizontalAlignment {
    private enum UnderlineLeading: AlignmentID {
        static func defaultValue(in d: ViewDimensions) -> CGFloat {
            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: [CGFloat] = [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(.linear)
        }
    }
}

struct TextGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            return Rectangle().fill(Color.clear).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]
                }.onTapGesture { self.activeIdx = self.idx }

            } else {
                content.onTapGesture { self.activeIdx = self.idx }
            }
        }
    }
}

这篇关于如何使视图在SwiftUI中的另一个视图的大小的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆