简体   繁体   English

如何在 SwiftUI 中查看另一个视图的大小

[英]How to make view the size of another view in 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.我正在尝试重新创建 Twitter iOS 应用程序的一部分来学习 SwiftUI,并且想知道如何将一个视图的宽度动态更改为另一个视图的宽度。 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()
            }
        }
    }
}

I have written a detailed explanation about using GeometryReader, view preferences and anchor preferences.我已经写了关于使用 GeometryReader、视图首选项和锚点首选项的详细说明。 The code below uses those concepts.下面的代码使用了这些概念。 For further information on how they work, check this article I posted: https://swiftui-lab.com/communicating-with-the-view-tree-part-1/有关它们如何工作的更多信息,请查看我发布的这篇文章: https : //swiftui-lab.com/communicating-with-the-view-tree-part-1/

The solution below, will properly animate the underline:下面的解决方案将正确地为下划线设置动画:

在此处输入图片说明

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.事实上,WWDC2019 会议 237(使用 SwiftUI 构建自定义视图)解释了视图会持续传达其大小。 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.我怀疑 anchorPreference 与它有关。 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. API 是公开的,但要掌握那些长函数原型的工作原理……我现在没有时间做这件事。

I think Apple has left this undocumented to force us rethink the whole framework and forget about "old" UIKit habits and start thinking declaratively.我认为 Apple 没有将其记录在案,以迫使我们重新思考整个框架并忘记“旧”的 UIKit 习惯并开始声明式思考。 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.我希望 Apple 能够在不久的将来记录偏好。 I have been experimenting with custom PreferenceKey and it looks interesting.我一直在尝试自定义 PreferenceKey,它看起来很有趣。

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).您需要两个维度(文本的 x 位置和宽度)。 One I get it fair and square, the other seems a bit of a hack.一个我认为公平公正,另一个似乎有点黑客。 Nevertheless, it works perfectly.尽管如此,它还是完美的。

The x position of the text I solved it by creating a custom horizontal alignment.我通过创建自定义水平对齐方式解决了文本的 x 位置。 More information on that check session 237 (at minute 19:00).有关该检查会话 237(在 19:00 分钟)的更多信息。 Although I recommend you watch the whole thing, it sheds a lot of light on how the layout process works.尽管我建议您观看整个过程,但它对布局过程的工作原理有很多了解。

The width, however, I'm not so proud of... ;-) It requires DispatchQueue to avoid updating the view while being displayed.然而,宽度,我并不那么自豪...... ;-) 它需要 DispatchQueue 来避免在显示时更新视图。 UPDATE: I fixed it in the second implementation down below更新:我在下面的第二个实现中修复了它

First implementation第一次实施

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

Update: Better implementation without using DispatchQueue更新:在不使用 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.它不仅仅是可以让您装饰视图背景的修饰符。

The basic steps are:基本步骤是:

  1. Use Text("text").background(TextGeometry()) .使用Text("text").background(TextGeometry()) TextGeometry is a custom view that has a parent with the same size as the text view. TextGeometry 是一个自定义视图,它的父视图与文本视图的大小相同。 That is what .background() does.这就是 .background() 所做的。 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.在我的TextGeometry实现中,我使用 GeometryReader 来获取父级的几何图形,这意味着我获取了 Text 视图的几何图形,这意味着我现在有了宽度。
  3. Now to pass the width back, I am using Preferences .现在将宽度传递回来,我正在使用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.我创建了我的自定义PreferenceKey ,称为WidthPreferenceKey ,我在 TextGeometry 中使用它来将宽度“附加”到视图,因此可以在层次结构中读取更高的位置。
  4. Back in the ancestor, I use onPreferenceChange to detect changes in the width, and set the widths array accordingly.回到祖先中,我使用onPreferenceChange来检测宽度的变化,并相应地设置宽度数组。

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

First, to answer the question in the title, if you want to make a shape (view) fit to the size of another view, you can use an .overlay() .首先,回答标题中的问题,如果你想让一个形状(视图)适合另一个视图的大小,你可以使用.overlay() The .overlay() gets offered its size from the view it is modifying. .overlay()从它正在修改的视图中获得其大小。

In order to set offsets and widths in your Twitter recreation, you can use a GeometryReader .为了在您的 Twitter 娱乐中设置偏移量和宽度,您可以使用GeometryReader The GeometryReader has the ability to find its .frame(in:) another coordinate space. GeometryReader能够找到它的.frame(in:)另一个坐标空间。

You can use .coordinateSpace(name:) to identify the reference coordinate space.您可以使用.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)
                }
            }
        }
    }
}
  1. The underline view is is a 2 point high Rectangle , put in an .overlay() on top of the HStack .underline视图是有2点高Rectangle ,放在一个.overlay()上的顶部HStack
  2. The underline view is aligned to .bottomLeading , so that we can programmatically set its .padding(.leading, _) using a @State value. underline视图与.bottomLeading对齐,因此我们可以使用@State值以编程方式设置其.padding(.leading, _)
  3. The underline view's .frame(width:) is also set using a @State value.下划线视图的.frame(width:)也使用@State值设置。
  4. The HStack is set as the .coordinateSpace(name: "container") so we can find the frame of our buttons relative to this. HStack被设置为.coordinateSpace(name: "container")所以我们可以找到相对于它的按钮的框架。
  5. The MoveUnderlineButton uses a GeometryReader to find its own width and minX in order to set the respective values for the underline view MoveUnderlineButton使用GeometryReader找到它自己的widthminX ,以便为underline视图设置相应的值
  6. The MoveUnderlineButton is set as the .overlay() for the Text view containing the text of that button so that its GeometryReader inherits its size from that Text view. MoveUnderlineButton被设置为包含该按钮文本的Text视图的.overlay() ,以便其GeometryReader从该Text视图继承其大小。

使用 Underbar 进行分段

Give this a try:试试这个:

import SwiftUI

var titles = ["Tweets", "Tweets & Replies", "Media", "Likes"]

struct GridViewHeader : View {

    @State var selectedItem: String = "Tweets"

    var body: some View {
        HStack(spacing: 20) {
            ForEach(titles.identified(by: \.self)) { title in
                HeaderTabButton(title: title, selectedItem: self.$selectedItem)
                }
                .frame(height: 50)
        }.padding(.horizontal, 10)

    }
}

struct HeaderTabButton : View {
    var title: String

    @Binding var selectedItem: String

    var isSelected: Bool {
        selectedItem == title
    }

    var body: some View {
        VStack {
            Button(action: { self.selectedItem = self.title }) {
                Text(title).fixedSize(horizontal: true, vertical: false)

                Rectangle()
                    .frame(height: 2, alignment: .bottom)
                    .relativeWidth(1)
                    .foregroundColor(isSelected ? Color.accentColor : Color.clear)

            }
        }
    }
}

And here's what it looks like in preview:这是预览中的样子: 预览画面

Let me modestly suggest a slight modification of this bright answer : Version without using preferences:让我谦虚地建议稍微修改一下这个明亮的答案:不使用首选项的版本:

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 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 {
        var w: CGFloat = 0
        return Group {
            if activeIdx == idx {
                content.alignmentGuide(.underlineLeading) { d in
                    w = d.width
                    return d[.leading]
                }.onTapGesture { self.activeIdx = self.idx }.onAppear(perform: {self.widths[self.idx] = w})

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

Version using preferences and GeometryReader :使用首选项和GeometryReader

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, widthStorage: $w))

                Spacer()

                Text("Tweets & Replies")
                    .modifier(MagicStuff(activeIdx: $activeIdx, idx: 1, widthStorage: $w))

                Spacer()

                Text("Media")
                    .modifier(MagicStuff(activeIdx: $activeIdx, idx: 2, widthStorage: $w))

                Spacer()

                Text("Likes")
                    .modifier(MagicStuff(activeIdx: $activeIdx, idx: 3, widthStorage: $w))

                }
                .frame(height: 50)
                .padding(.horizontal, 10)
            Rectangle()
                .frame(width: w[activeIdx],  height: 2)
                .animation(.linear)
        }
    }
}

struct MagicStuff: ViewModifier {
    @Binding var activeIdx: Int
    let idx: Int
    @Binding var widthStorage: [CGFloat]

    func body(content: Content) -> some View {
        Group {

            if activeIdx == idx {
                content.background(GeometryReader { geometry in
                    return Color.clear.preference(key: WidthPreferenceKey.self, value: geometry.size.width)
                })
                .alignmentGuide(.underlineLeading) { d in
                    return d[.leading]
                }.onTapGesture { self.activeIdx = self.idx }
                    .onPreferenceChange(WidthPreferenceKey.self, perform: { self.widthStorage[self.idx] = $0 })


            } else {
                content.onTapGesture { self.activeIdx = self.idx }.onPreferenceChange(WidthPreferenceKey.self, perform: { self.widthStorage[self.idx] = $0 })
            }
        }
    }
}

Here's a super simple solution, although it doesn't account for the tabs being stretched full width - but that should just be minor additional math for calculating the padding.这是一个超级简单的解决方案,虽然它没有考虑到选项卡被全宽拉伸 - 但这应该只是计算填充的次要附加数学。

import SwiftUI

struct HorizontalTabs: View {

  private let tabsSpacing = CGFloat(16)

  private func tabWidth(at index: Int) -> CGFloat {
    let label = UILabel()
    label.text = tabs[index]
    let labelWidth = label.intrinsicContentSize.width
    return labelWidth
  }

  private var leadingPadding: CGFloat {
    var padding: CGFloat = 0
    for i in 0..<tabs.count {
      if i < selectedIndex {
        padding += tabWidth(at: i) + tabsSpacing
      }
    }
    return padding
  }

  let tabs: [String]

  @State var selectedIndex: Int = 0

  var body: some View {
    VStack(alignment: .leading) {
      HStack(spacing: tabsSpacing) {
        ForEach(0..<tabs.count, id: \.self) { index in
          Button(action: { self.selectedIndex = index }) {
            Text(self.tabs[index])
          }
        }
      }
      Rectangle()
        .frame(width: tabWidth(at: selectedIndex), height: 3, alignment: .bottomLeading)
        .foregroundColor(.blue)
        .padding(.leading, leadingPadding)
        .animation(Animation.spring())
    }
  }
}

HorizontalTabs(tabs: ["one", "two", "three"]) renders this: HorizontalTabs(tabs: ["one", "two", "three"])呈现这个:

截屏

You just need to specify a frame with a height within it.您只需要指定一个高度在其中的框架。 Here's an example :这是一个例子:

VStack {
    Text("First Text Label")

    Spacer().frame(height: 50)    // This line

    Text("Second Text Label")
}

This solution is very wonderful. 这个解决方案非常棒。

But it became a compilation error now, it corrected.但是现在变成了编译错误,改正了。 (Xcode11.1) (Xcode11.1)

This is a whole code.这是一个完整的代码。

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 {
    typealias Value = CGFloat
    static var defaultValue = CGFloat(0)
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}


struct HorizontalTabsView : 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(.default)
        }
    }
}

struct TextGeometry: View {
    var body: some View {
        GeometryReader { geometry in
            return Rectangle()
                .foregroundColor(.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 }
            }
        }
    }
}

struct HorizontalTabsView_Previews: PreviewProvider {
    static var previews: some View {
        HorizontalTabsView()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM