✨dynamic underline
同一條底線,可以在不同的文字下面遊走。這個例子只用到 @State 變數,並沒有用到 PreferenceKey。
Last updated
同一條底線,可以在不同的文字下面遊走。這個例子只用到 @State 變數,並沒有用到 PreferenceKey。
Last updated
在 Text 上面覆蓋 (overlay) 一個「透明的按鈕」,按下後會回報自己的 frame 給 parent view 的 @State 變數 (buttonFrame),parent view 再根據這個資料去更新底線的位置。
因為這個例子是按下按鈕後,才會通知 parent view 做更新,並不是一開始佈局 (layout) 的時候就需要決定底線的位置,所以並不需要用到 🅿️ PreferenceKey 。
當然,這個例子也可以用像「💈 動態框線」一樣的方法,一開始佈局的時候就把所有 Text 的 frame 全部記錄下來 (這時就要用到 🅿️ PreferenceKey),然後再動態決定底線的位置。
/*
What is Geometry Reader in SwiftUI?
https://stackoverflow.com/a/56729880/5409815
How to make view the size of another view in SwiftUI
https://stackoverflow.com/a/61708675/5409815
*/
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
// view body
var body: some View {
Tabs() // 🌅 Tabs
.shadow(color: .black, radius: 6, x: 6, y: 6)
}
}
// live view
PlaygroundPage.current.setLiveView(ContentView())
// 🌅 Tabs
struct Tabs: View {
// ⭐️ 存放目前按下按鈕的 frame (在參考座標系 "container" 中)
@State private var currentFrame: CGRect = .zero
// ⭐️ 畫底線
func underline(_ color: Color = .orange) -> some View {
color
// ⭐️ 1. 使用回報的 width。
.frame(width: currentFrame.width, height: 2)
// ⭐️ 2 將 `underline` 向右推,對齊 `currentFrame` 的前沿。
.padding(.leading, currentFrame.minX)
}
// view body
var body: some View {
HStack {
// 🌅 MyButton
// ⭐️ 1. 放 `MyButton`,按一下就會回報自己的 frame。
// 2. 設定回報在 "container" 中的座標。
MyButton(clickToReport: $currentFrame, label: "Tweets", in: "container")
MyButton(clickToReport: $currentFrame, label: "Tweets & Replies", in: "container")
MyButton(clickToReport: $currentFrame, label: "Media", in: "container")
MyButton(clickToReport: $currentFrame, label: "Likes", in: "container")
}// HStack (container)
// ⭐️ 3. 定義 "container" 座標系統。
.coordinateSpace(name: "container")
// ⭐️ 4. 放 `underline`,對齊左下角。
.overlay(underline(.yellow), alignment: .bottomLeading)
.padding().background(Color.gray)
.animation(.spring())
}
}
// 🌅 MyButton
// 按下按鈕時,會回報自己在參考座標系中的 frame
struct MyButton: View {
// ⭐️ send `MyButton`'s frame back to parent view
@Binding var buttonFrame: CGRect
// local variables
let label: String // 按鈕文字
let space: CoordinateSpace // `buttonFrame` 的參考座標系
// init
init(clickToReport frame: Binding<CGRect>, label: String, in space: CoordinateSpace = .global) {
self._buttonFrame = frame
self.label = label
self.space = space
}
init(clickToReport frame: Binding<CGRect>, label: String, in spaceName: String) {
self._buttonFrame = frame
self.label = label
self.space = .named(spaceName)
}
// view body
var body: some View {
// ⭐️ 1. 先用 Text 定義 frame 的大小
Text(label)
// ⭐️ 2. 在 Text 前面放一個透明的 Button
.overlay(GeometryReader { geo in
Button(action: {
// ⭐️ 3. 設定按下按鈕時,會回報自己在參考座標系中的 frame
self.buttonFrame = geo.frame(in: self.space)
}, label: { Color.clear }) // 透明色
})
}
}