Last active
February 13, 2020 13:08
-
-
Save DevAndArtist/9517819907e43b6d0a27c7b2374d0f51 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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