Anchor preferences in SwiftUI

栏目: IT技术 · 发布时间: 4年前

内容简介:Today we will continue mastering view preferences inFirst of all, I want to ask you to check the post about view preferences if you are not familiar with these API. Anchor preferences use a very similar API. The only difference is that it is tuned to pass

Today we will continue mastering view preferences in SwiftUI that we touched a few weeks ago. Anchor preferences are another type of view preferences provided by SwiftUI . The main goal of anchor preferences is to pass layout data like bounds, center coordinates, etc. to its parent view.

Basics

First of all, I want to ask you to check the post about view preferences if you are not familiar with these API. Anchor preferences use a very similar API. The only difference is that it is tuned to pass layout-specific data.

To learn more about the benefits of preferences in SwiftUI, take a look at my “The magic of view preferences in SwiftUI” post.

Let’s build a simple view that shows text and passes its bounds to the ancestor. The parent view will draw an overlaying rectangle in that position.

struct BoundsPreferenceKey: PreferenceKey {
    typealias Value = Anchor<CGRect>?

    static var defaultValue: Value = nil

    static func reduce(
        value: inout Value,
        nextValue: () -> Value
    ) {
        value = nextValue()
    }
}

struct ExampleView: View {
    var body: some View {
        ZStack {
            Color.yellow
            Text("Hello World !!!")
                .anchorPreference(
                    key: BoundsPreferenceKey.self,
                    value: .bounds
                ) { $0 }
        }
        .overlayPreferenceValue(BoundsPreferenceKey.self) { preferences in
            GeometryReader { geometry in
                preferences.map {
                    Rectangle()
                        .stroke()
                        .frame(
                            width: geometry[$0].width,
                            height: geometry[$0].height
                        )
                        .offset(
                            x: geometry[$0].minX,
                            y: geometry[$0].minY
                        )
                }
            }
        }
    }
}

As you can see in the example above, we still use the PreferenceKey protocol to create an anchor preference key. It has two requirements: default value and reduce function. Reduce function allows us to merge multiple values that appear from different views. We can replace the current value with the new one for now. We will see more advanced usage of reduce function later in the post.

Anchor preferences use opaque Anchor type. You can’t merely use Anchor type anywhere in the app. You have to use it in pair with GeometryProxy provided by GeometryReader . You can use the subscript of GeometryProxy to resolve anchor and access wrapped CGRect value. As a bonus, SwiftUI will convert a coordinate space between views while solving anchor, and you don’t need to do it manually.

We use the anchorPreference modifier to define the type of PreferenceKey and the value we want to gather. It can be bounds, center, leading, trailing, top, bottom. We also pass a closure that transforms provided anchor value. In this case, we don’t need any transformation and return the CGRect value as is.

In the end, we use overlayPreferenceValue on ancestor view to access gathered preference values and return overlay view. As I mentioned before, we need a GeometryProxy to resolve an anchor. That’s why we use here GeometryReader .

We can easily use border modifier on the text view to achieve the same result, but I’ve done it to show you the basics of anchor preferences.

Advanced usage

Now we can move to more advanced usage of anchor preferences. As an example, we will build a grid view. We will need to gather the size of every view inside the grid to calculate its positions. Let’s start by defining the PreferenceKey for our grid view.

struct SizePreferences<Item: Hashable>: PreferenceKey {
    typealias Value = [Item: CGSize]

    static var defaultValue: Value { [:] }

    static func reduce(
        value: inout Value,
        nextValue: () -> Value
    ) {
        value.merge(nextValue()) { $1 }
    }
}

As you can see here, we will store the dictionary that represents an item and its size. In the reduce function, we merge old and new dictionaries by overriding new values. Now we can define our grid view.

struct Grid<Data: RandomAccessCollection, ElementView: View>: View where Data.Element: Hashable {
    private let data: Data
    private let itemView: (Data.Element) -> ElementView

    @State private var preferences: [Data.Element: CGRect] = [:]

    init(_ data: Data, @ViewBuilder itemView: @escaping (Data.Element) -> ElementView) {
        self.data = data
        self.itemView = itemView
    }

    var body: some View {
        GeometryReader { geometry in
            ZStack(alignment: .topLeading) {
                ForEach(self.data, id: \.self) { item in
                    self.itemView(item)
                        .alignmentGuide(.leading) { _ in
                            -self.preferences[item, default: .zero].origin.x
                    }.alignmentGuide(.top) { _ in
                        -self.preferences[item, default: .zero].origin.y
                    }.anchorPreference(
                        key: SizePreferences<Data.Element>.self,
                        value: .bounds
                    ) {
                        [item: geometry[$0].size]
                    }
                }
            }
        }
    }
}

We use ZStack with top leading alignment. It allows us to position items inside in an effortless way. Instead of using the offset modifier which doesn’t affect the layout, we use overridden alignment guides to position our child views. We also resolve our anchors here, because we already have access to the instance of GeometryProxy .

To learn more about the benefits of alignment guides in SwiftUI, take a look at my “Alignment guides in SwiftUI” post.

var body: some View {
    GeometryReader { geometry in
        ZStack(alignment: .topLeading) {
            ...
        }
        .onPreferenceChange(SizePreferences<Data.Element>.self) { sizes in
            var newPreferences: [Data.Element: CGRect] = [:]
            var bounds: [CGRect] = []
            for item in self.data {
                let size = sizes[item, default: .zero]
                let rect: CGRect
                if let lastBounds = bounds.last {
                    if lastBounds.maxX + size.width > geometry.size.width {
                        let origin = CGPoint(x: 0, y: lastBounds.maxY)
                        rect = CGRect(origin: origin, size: size)
                    } else {
                        let origin = CGPoint(x: lastBounds.maxX, y: lastBounds.minY)
                        rect = CGRect(origin: origin, size: size)
                    }
                } else {
                    rect = CGRect(origin: .zero, size: size)
                }
                bounds.append(rect)
                newPreferences[item] = rect
            }
            self.preferences = newPreferences
        }
    }
}

As the last step, we calculate bounds for every item using GeometryProxy and gathered sizes. Let’s take a look at the final result.

struct RootView: View {
    @State private var cards: [String] = [
        "Lorem", "ipsum", "is", "placeholder", "text", "!!!"
    ]

    var body: some View {
        Grid(cards) { card in
            Text(card)
                .frame(width: 120, height: 120)
                .background(Color.orange)
                .cornerRadius(8)
                .padding(4)
        }
    }
}

Anchor preferences in SwiftUI

Conclusion

SwiftUI provides us so many great tools that we can use to build impressive views. Anchor preferences feature is one of the powerful hidden gems of SwiftUI . I hope you enjoy the post. Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading, and see you next week!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

永无止境

永无止境

[美] 道格拉斯•艾德华兹 / 刘纯毅 / 中信出版社 / 2012-12-15 / 59.00元

★ 值得中国初创公司反复思考的企业传记 ★ 互联网行业必读书 ★ Google高管揭开Google的神秘面纱 ★ 探寻“G力量”重塑人类知识景观的心路历程 ★ Google走过的路,Google未来的路 ★ 编辑推荐: 它是目前被公认为全球最大的搜索引擎!它是互联网上五大最受欢迎的网站之一! 它在操作界面中提供多达30余种语言选择,在全球范围内拥有无数用户......一起来看看 《永无止境》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具