💈CircleText
在文字外面加一個有顏色的圓圈,圓圈會隨著文字變大而變大。
Last updated
在文字外面加一個有顏色的圓圈,圓圈會隨著文字變大而變大。
Last updated
在一開始佈局的時候,請 Text 回報自己的「最大邊」(定義在:🌀 GeometryProxy + 🅿️ Rectangular) 給 MaxSide (🅿️ PreferenceKey),然後 CircleText 再透過 .onPreferenceChange(),用回報來的值更新自己的 maxSide @State 變數,進一步推動 Text 更新自己的 frame。
// live view
struct ContentView: View {
var body: some View {
HStack {
Group {
CircleText("Hello, World") // 🌅 CircleText
CircleText("Hello", color: .blue)
}// Group
// ⭐️ 這裡可以確定 CircleText's frame 已經包含整個圓。
.border(Color.black)
}// HStack (container)
.padding()
.background(Color.gray)
.shadow(radius: 8).shadow(radius: 8)
}
}
PlaygroundPage.current.setLiveView(ContentView())
// 🌅 CircleText
struct CircleText: View {
// ⭐️ Text 回報的最大邊長
@State private var maxSide: CGFloat?
// local variables
let text : String // text of Text
let color: Color // bg color of Text
// init
init(_ text: String, color: Color = .pink) {
self.text = text
self.color = color
}
// view body
var body: some View {
Text(text).padding()
// ⭐️ 1. set (optional) frame
.frame(width: maxSide, height: maxSide)
// ⚠️ 2. 要先設定好 frame 再加上 Circle,不然 Circle 只有
// Text + padding 的高度 ‼️
.background(Circle().fill(color))
// ⭐️ 3. 回報自己的「最大邊」給 📦 MaxSide
// - report(to:): 🌀View + pref
// - $0.maxSide : 🌀GeometryProxy + 🅿️Rectangular
.report(to: MaxSide.self) { $0.maxSide }
// ⭐️ 4. 母視圖根據回報的值,更新自己的 @State 變數 `maxSide`
.onPreferenceChange(MaxSide.self) { self.maxSide = $0 }
}
}
// ⭐️ PreferenceKey 所取的名字最好是跟母視件的 @State 變數一致,例如:
// 此例要找的是 Text 的最大邊,所以取 CircleText 的 @State 變數
// 名為: `maxSide`,而這個 PreferenceKey 就取名為: `MaxSide`。
typealias MaxSide = FirstNonNil<CGFloat> // 📦 FirstNonNil<T>
🅿️ PreferenceKey (FirstNonNil) ⭐️
初版
2020.10.15:
將 SizePreferenceKey 改為 generic FirstNonNil<T>
新增 generic view.report(to: key) {...} 統一各種 view.report 函數。
/*
* Required:
* - 🌀CGRect + .frame
* - 🌀GeometryProxy + 🅿️ Rectangular
*/
import SwiftUI
import PlaygroundSupport
// ⭐️ custom preference key
struct SizePreferenceKey: PreferenceKey {
// default value
static let defaultValue: CGSize? = nil
// combine values from different child views
static func reduce(value: inout CGSize?, nextValue: () -> CGSize?) {
value = value ?? nextValue() // nil or first non-nil value
}
}
// 🌅 CircleText
struct CircleText: View {
// ⭐️ Text's favorite size (a square)
@State private var size: CGSize?
// local variables
let text : String // text of Text
var color: Color = .pink // bg color of Text
// view body
var body: some View {
Text(text)
.padding()
// ⭐️ 1. set optional frame (🌀CGRect + .frame)
.frame(size)
// ⭐️ 2. set bg color
.background(GeometryReader { geo in // ⭐️ 3. read Text's size
self.color
// ⭐️ 4. set preference:
// tell parent view its favorite size (a square)
// - 🌀GeometryProxy + 🅿️ Rectangular
.preference(key: SizePreferenceKey.self, value: geo.boundingSquare.size)
})
.clipShape(Circle())
// ⭐️ 5. parent view update itself in response to the preference change
.onPreferenceChange(SizePreferenceKey.self) { self.size = $0 }
}
}
// live view
struct ContentView: View {
var body: some View {
HStack {
Group {
// 🌅 CircleText
CircleText(text: "Hello, World")
CircleText(text: "Hello", color: .blue)
}// Group
.border(Color.black)
}// HStack (container)
.padding()
.background(Color.gray)
.shadow(radius: 8).shadow(radius: 8)
}
}
PlaygroundPage.current.setLiveView(ContentView())
/*
* ⭐️ Required:
* - 🌀GeometryProxy + 🅿️Rectangular
* - 📦FirstNonNil
* - 🌀View + pref
*/
import SwiftUI
import PlaygroundSupport
// ⭐️ PreferenceKey 所取的名字最好是跟 parent view 內的 @State 變數一致,
// 如此語意的表現會更好,例如:
// 這個例子要取的是 child view (Text) 長寬中的最大邊,所以我們將
// parent view (CircleText) 的 @State 變數取名為:
// - `maxSide`,
// 因此,我們將這個 PreferenceKey 取名為:
// - `MaxSide`。
typealias MaxSide = FirstNonNil<CGFloat> // 📦 FirstNonNil
// 🌅 CircleText
struct CircleText: View {
// ⭐️ Text 回報的最大邊長
@State private var maxSide: CGFloat?
// local variables
let text : String // text of Text
let color: Color // bg color of Text
// init
init(_ text: String, color: Color = .pink) {
self.text = text
self.color = color
}
// view body
var body: some View {
Text(text).padding()
// ⭐️ 1. set (optional) frame
.frame(width: maxSide, height: maxSide)
// ⚠️ 2. 要先設定好 frame 再加上 Circle,不然 Circle 只有
// Text + padding 的高度 ‼️
.background(Circle().fill(color))
// ⭐️ 3. 回報自己的「最大邊」給 MaxSide
// - report(to:): 🌀View + pref
// - $0.maxSide : 🌀GeometryProxy + 🅿️Rectangular
.report(to: MaxSide.self) { $0.maxSide }
// ⭐️ 4. 母視圖根據回報的值,更新自己的 @State 變數 `maxSide`
.onPreferenceChange(MaxSide.self) { self.maxSide = $0 }
}
}
// live view
struct ContentView: View {
var body: some View {
HStack {
Group {
CircleText("Hello, World") // 🌅 CircleText
CircleText("Hello", color: .blue)
}// Group
// ⭐️ 這裡可以確定 CircleText's frame 已經包含整個圓。
.border(Color.black)
}// HStack (container)
.padding()
.background(Color.gray)
.shadow(radius: 8).shadow(radius: 8)
}
}
PlaygroundPage.current.setLiveView(ContentView())