Skip to content

Instantly share code, notes, and snippets.

@DevAndArtist
Last active February 13, 2020 13:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DevAndArtist/9517819907e43b6d0a27c7b2374d0f51 to your computer and use it in GitHub Desktop.
Save DevAndArtist/9517819907e43b6d0a27c7b2374d0f51 to your computer and use it in GitHub Desktop.
// This code uses 3 HIDDEN SwiftUI types, there is no guarantee
// that it will pass app review, so please use it with caution.
//
// * SwiftUI._PreferenceValue<Key>
// * SwiftUI._DelayedPreferenceView<Key, Content>
// * SwiftUI._PreferenceValue<Key>
//
// Feedback ID for an official support: (FB7577482)
//
// ⚠️ WARNING:
// There is chance that you might create an attribute cycle,
// if you attempt to nest multiple `PreferenceView`'s.
//
// ```
// === AttributeGraph: cycle detected through attribute XX ===
// ```
//
// ----------------------------------------------------------------------------
//
// 🙁 In Xcode 11.3 (11C29) and 11.3.1 (11C504) the invalid example produces an
// infinite cycle.
//
// 🤔 In Xcode 11.4 beta 1 the invalid example will log two cycles and
// eventually resolve the views.
//
// 💡 The attribute cycle might be just an internal bug, and the view should
// probably just work.
import SwiftUI
public struct Generator<Key> where Key: PreferenceKey {
let preferenceValue: SwiftUI._PreferenceValue<Key>
public func generate<V>(
@ViewBuilder transform: @escaping (Key.Value) -> V
) -> some View where V: View {
SwiftUI._PreferenceReadingView(value: preferenceValue, transform: transform)
}
}
public struct PreferenceView<Key, Content>:
View
where
Key: PreferenceKey,
Content: View
{
private typealias _DelayedPreferenceView = SwiftUI._DelayedPreferenceView
private typealias _PreferenceValue = SwiftUI._PreferenceValue<Key>
let content: (Generator<Key>) -> Content
public init(
key: Key.Type = Key.self,
@ViewBuilder content: @escaping (Generator<Key>) -> Content
) {
self.content = content
}
public var body: some View {
_DelayedPreferenceView { (preferenceValue: _PreferenceValue) in
self.content(Generator(preferenceValue: preferenceValue))
}
}
}
struct SizesKey: PreferenceKey {
typealias Value = [Anchor<CGRect>]
static var defaultValue: Value = []
static func reduce(value: inout Value, nextValue: () -> Value) {
value.append(contentsOf: nextValue())
}
}
struct StringsKey: PreferenceKey {
typealias Value = [String]
static var defaultValue: Value = []
static func reduce(value: inout Value, nextValue: () -> Value) {
value.append(contentsOf: nextValue())
}
}
struct TextWithExternalSize: View {
let generator: Generator<SizesKey>
init(_ generator: Generator<SizesKey>) {
self.generator = generator
}
var body: some View {
Text("A").background(
GeometryReader { proxy in
self.generator.generate { preference in
preference.first.map { anchor in
Color.orange.frame(
width: proxy[anchor].width,
height: proxy[anchor].height
)
}
}
}
)
}
}
struct ValidExampleView: View {
var body: some View {
PreferenceView(key: SizesKey.self) { generator in
VStack(spacing: 8) {
// The view can be placed side by side in a stack.
// It does not matter if the view is placed before
// the view which will emit the preference we're
// interested in.
TextWithExternalSize(generator)
TextWithExternalSize(generator)
Text("Swift Code")
.border(Color.red)
.anchorPreference(
key: SizesKey.self,
value: .bounds,
transform: { anchor in
[anchor]
}
)
TextWithExternalSize(generator)
TextWithExternalSize(generator)
}
}
}
}
struct InvalidExampleView: View {
var body: some View {
PreferenceView(key: StringsKey.self) { g2 in
HStack {
g2.generate { preference in
preference.first.map(Text.init)
}
PreferenceView(key: SizesKey.self) { g1 in
VStack(spacing: 8) {
Text("Swift Code")
.border(Color.red)
.preference(key: StringsKey.self, value: ["X"])
.anchorPreference(
key: SizesKey.self,
value: .bounds,
transform: { anchor in
[anchor]
}
)
TextWithExternalSize(g1)
}
}
}
}
}
}
struct ContentView: View {
// Flip this constant to show invalid example with a cycle.
let useValidExample = true
@ViewBuilder
var body: some View {
if useValidExample {
ValidExampleView()
} else {
InvalidExampleView()
}
}
}
struct ContentView_PreviewProvider: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment