โจviews with same width
Last updated
Last updated
ๅฉ็จ PreferenceKey ไพๅฐ้ฝๆฌไพๅฏฌๅบฆไธๅ็ TextFieldใ
// โญ๏ธ PreferenceKey๏ผ
// 1. ๆบๅๆฅๆถๆๆ Text ็ๆๅคงๅฏฌๅบฆใ
typealias MaxWidth = MaxValue<CGFloat> // ๐ฆ MaxValue<T: FloatingPoint>
// live view
struct ContentView: View {
var body: some View {
VStack {
MyForm() // ๐
MyForm
Color.pink.cornerRadius(12)
}
.padding()
.background(Color.gray)
.cornerRadius(16)
.shadow(color: .black, radius: 6, x: 6, y: 6)
}
}
็ถไธ้ๅงๅจไฝๅฑ (layout) ็ๆๅ๏ผๅฐฑ่ฆไฟฎๆน child view ็ๅฐบๅฏธ็่ฉฑ๏ผ้ๅธธไธ่ฝๅจ child view ็ ๐ GeometryReader ไธญ็ดๆฅไฟฎๆน parent view ็ @State ่ฎๆธใ ๐ SwiftUI GeometryReader failed to update its parent's state
้ๆๆๅฅฝๆฏ๏ผ
ไฝฟ็จ ๐ ฟ๏ธ PreferenceKey ๏ผ่ฎ child view ้ๆฅๅ่จด parent view ๅฎ่ฆ็ๅฐบๅฏธๆฏไป้บผ
child view ไบๅ ็จ parent view ็ @State ่ฎๆธ่จญๅฎ่ชๅทฑ็ๅฐบๅฏธ
ๅ็ฑ parent view ้้ .onPreferenceChange() ไพไธปๅๆดๆฐๅฎ็ @State ่ฎๆธ๏ผ็ถๅพๅธถๅ child view ็ๆดๆฐใ
// ๐
MyForm
struct MyForm: View {
@State var value1 = ""
@State var value2 = ""
@State var value3 = ""
// โญ๏ธ label ็็ตฑไธๅฏฌๅบฆ
@State private var labelWidth: CGFloat?
var body: some View {
Form {
// ๐
MyTextField
MyTextField(
labelWidth : $labelWidth, // ๆฟๆฅไพ่ชๆฏ็ฉไปถ็ใ็ตฑไธๅฏฌๅบฆใ
label : "ๅงๅ",
placeholder: "่ซ่ผธๅ
ฅๆจ็ๅๅญ",
text : $value1
)
MyTextField(
labelWidth : $labelWidth,
label : "้ป่ฉฑ",
placeholder: "0900-000-123",
text : $value2
)
MyTextField(
labelWidth : $labelWidth,
label : "้ปๅญไฟก็ฎฑ",
placeholder: "email@company.com",
text : $value3
)
}// Form
.cornerRadius(12)
// โญ๏ธ 2. ๆฏ็ฉไปถๆ นๆๅๅ ฑ็ๅฏฌๅบฆ๏ผๆบๅๆดๆฐ่ชๅทฑ็ labelWidth
.onPreferenceChange(MaxWidth.self) { self.labelWidth = $0 }
}
}
// ๐
MyTextField
// -----------------------------------------------------------
// ไธปๅ่ฝ๏ผ
// 1. ๆๅ `MaxWidth` (PreferenceKey) ๅ ฑๅ่ชๅทฑ label ็ๅฏฌๅบฆใ
// 2. ่จญๅฎ่ชๅทฑ label ็ๅฏฌๅบฆ็บ `labelWidth` (โญ๏ธ ไพ่ช parent view)ใ
// (ๅ้ๅง็บ nil๏ผ่กจ็คบไธๅผทๅถ่จญๅฎๅฏฌๅบฆ๏ผ็ฑ label ่ชๅทฑๆฑบๅฎๅฏฌๅบฆ)
// 3. ๅช่ฆไพ่ช parent view ็ `labelWidth` ๆดๆฐไบ๏ผ่ชๅทฑๅฐฑๆ่ท่ๆดๆฐใ
// -----------------------------------------------------------
struct MyTextField: View {
@Binding var labelWidth: CGFloat? // label's width (โญ๏ธ ็ฑ parent @State ๆฑบๅฎ)
@Binding var text : String // text field's content (โญ๏ธ ๅไธ)
let label : String // text field's label
let placeholder: String // text field's placeholder
init(labelWidth: Binding<CGFloat?>, label: String, placeholder: String, text: Binding<String>) {
self._labelWidth = labelWidth
self._text = text
self.label = label
self.placeholder = placeholder
}
var body: some View {
HStack {
// label
Text(label)
// โญ๏ธ 3. ๅ MaxWidth ๅๅ ฑ่ชๅทฑ็ๅฏฌๅบฆ
.reportWidth(to: MaxWidth.self) // ๐View + pref, ๐ฆ MaxWidth
// ่จญๅฎๅฐบๅฏธ่้ ๅณๅฐ้ฝ
.frame(width: labelWidth, alignment: .trailing)
// text field
TextField(placeholder, text: $text)
.style(.rounded) // ๐TextField + style
}
}
}
2020.10.13๏ผ โ ๐ SizeReporter โ ๐View + report(width: key)
2020.10.14๏ผ โ๏ธ ๅพฎไฟฎๆน๏ผ่ฎ่ช็พฉๆด้้ ใ โ๏ธ view.report(width: key) -> view.reportWidth(to: key) โ view.reportFrame(to: key, in: space)
2020.10.15๏ผ โ๏ธ MaxWidth ๆน็บ generic MaxValue<T: FloatingPoint>
import SwiftUI
import PlaygroundSupport
// ๐ฆ MaxWidth
struct MaxWidth: PreferenceKey {
// default value (nil == not set)
static let defaultValue: CGFloat? = nil
// โญ๏ธ choose max width
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
if let next = nextValue() {
value = max(next , value ?? 0)
}
}
}
// ๐
SizeReporter
struct SizeReporter: View {
var body: some View {
GeometryReader { geo in // โญ๏ธ 1. read Text's size
Color.clear
// โญ๏ธ 2. set preference:
// tell parent view its preferred width
// - ๐GeometryProxy + ๐
ฟ๏ธ Rectangular
.preference(key: MaxWidth.self, value: geo.width)
}
}
}
// ๐
TextFieldWithPreference
struct TextFieldWithPreference: View {
@Binding var width: CGFloat? // label's preferred width โญ๏ธ
@Binding var text : String // text field's content
let label: String // text field's label
let placeholder: String // text field's placeholder
init(_ width: Binding<CGFloat?>, label: String, placeholder: String, text: Binding<String>) {
self._width = width
self._text = text
self.label = label
self.placeholder = placeholder
}
var body: some View {
HStack {
// label
Text(label)
// โญ๏ธ 1. report size to parent
.background(SizeReporter())
// โญ๏ธ 2. set optional width โญ๏ธ 3. set alignment
.frame(width: width, alignment: .trailing)
// text field
TextField(placeholder, text: $text)
.style(.rounded) // ๐TextField + style
}
}
}
// ๐
MyForm
struct MyForm: View {
@State var value1 = ""
@State var value2 = ""
@State var value3 = ""
// textfield label's preferred width
@State private var width: CGFloat?
var body: some View {
Form {
TextFieldWithPreference($width, label: "First Item", placeholder: "Enter first item", text: $value1)
TextFieldWithPreference($width, label: "Second Item", placeholder: "Enter second item", text: $value2)
TextFieldWithPreference($width, label: "Third Item", placeholder: "Enter third item", text: $value3)
}// Form
.cornerRadius(12)
.onPreferenceChange(MaxWidth.self) { self.width = $0 }
}
}
struct ContentView: View {
var body: some View {
MyForm()
.padding()
.background(Color.gray)
.cornerRadius(16)
.shadow(color: .black, radius: 6, x: 6, y: 6)
}
}
// live view
PlaygroundPage.current.setLiveView(ContentView())
import SwiftUI
import PlaygroundSupport
// ๐ฆ MaxWidth
public struct MaxWidth: PreferenceKey {
// default value (nil == not set)
public static let defaultValue: CGFloat? = nil
// โญ๏ธ choose max width
public static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
if let next = nextValue() {
value = max(next , value ?? 0)
}
}
}
// ๐
MyTextField
// -----------------------------------------------------------
// ไธปๅ่ฝ๏ผ
// 1. ๆๅ `MaxWidth` (PreferenceKey) ๅ ฑๅ่ชๅทฑ label ็ๅฏฌๅบฆใ
// 2. ่จญๅฎ่ชๅทฑ label ็ๅฏฌๅบฆ็บ `labelWidth` (โญ๏ธ ไพ่ช parent view)ใ
// (ๅ้ๅง็บ nil๏ผ่กจ็คบไธๅผทๅถ่จญๅฎๅฏฌๅบฆ๏ผ็ฑ label ่ชๅทฑๆฑบๅฎๅฏฌๅบฆ)
// 3. ๅช่ฆไพ่ช parent view ็ `labelWidth` ๆดๆฐไบ๏ผ่ชๅทฑๅฐฑๆ่ท่ๆดๆฐใ
// -----------------------------------------------------------
struct MyTextField: View {
@Binding var labelWidth: CGFloat? // label's width (โญ๏ธ ็ฑ parent @State ๆฑบๅฎ)
@Binding var text : String // text field's content (โญ๏ธ ๅไธ)
let label : String // text field's label
let placeholder: String // text field's placeholder
init(labelWidth: Binding<CGFloat?>, label: String, placeholder: String, text: Binding<String>) {
self._labelWidth = labelWidth
self._text = text
self.label = label
self.placeholder = placeholder
}
var body: some View {
HStack {
// label
Text(label)
// โญ๏ธ 1. report text's width to `MaxWidth` PreferenceKey
// (wait for parent to update its `maxWidth`)
.reportWidth(to: MaxWidth.self) // ๐View + report, ๐ฆ MaxWidth
// โญ๏ธ 2. set width with `labelWidth`(optional), and alignment
.frame(width: labelWidth, alignment: .trailing)
// text field
TextField(placeholder, text: $text)
.style(.rounded) // ๐TextField + style
}
}
}
// ๐
MyForm
struct MyForm: View {
@State var value1 = ""
@State var value2 = ""
@State var value3 = ""
// โญ๏ธ textfield label's width
@State private var labelWidth: CGFloat?
var body: some View {
Form {
// ๐
MyTextField
// โญ๏ธ 1. report width to `MaxWidth`
// 2. set label's width to `labelWidth`
MyTextField(
labelWidth : $labelWidth,
label : "ๅงๅ",
placeholder: "่ซ่ผธๅ
ฅๆจ็ๅๅญ",
text : $value1
)
MyTextField(
labelWidth : $labelWidth,
label : "้ป่ฉฑ",
placeholder: "0900-000-123",
text : $value2
)
MyTextField(
labelWidth : $labelWidth,
label : "้ปๅญไฟก็ฎฑ",
placeholder: "email@company.com",
text : $value3
)
}// Form
.cornerRadius(12)
// โญ๏ธ 3. parent view responses to preference change
// (update its `labelWidth`)
.onPreferenceChange(MaxWidth.self) { self.labelWidth = $0 }
}
}
struct ContentView: View {
var body: some View {
VStack {
MyForm() // ๐
MyForm
Color.pink.cornerRadius(12)
}
.padding()
.background(Color.gray)
.cornerRadius(16)
.shadow(color: .black, radius: 6, x: 6, y: 6)
}
}
// live view
PlaygroundPage.current.setLiveView(ContentView())
import SwiftUI
import PlaygroundSupport
/*
* โญ๏ธ ไฝฟ็จ K: PreferenceKey ็ไธๅคง่ฆ็ด
* 1. PreferenceKey ๆฌ่บซ
* 2. parent view ็ @State ่ฎๆธ๏ผ้ๅธธๅๅฅ่ K.Value ็ธๅ
* 3. child view ็ @Binding ่ฎๆธ๏ผๅฏไปฅๆฟๆฅไพ่ช parent view ็ๆดๆฐ้็ฅใ
*
* โญ๏ธ ่็้็จไธ้จๆฒ๏ผ
* 1. ็ฑๅญ็ฉไปถๅๅ ฑๅผ็ตฆ K๏ผ็จ child.preference(key:value:) ๅๅ ฑ
* 2. ็ฑ K ่ฒ ่ฒฌ่็ๅๅ ฑ็ๅผ๏ผ็จ K.reduce() ่็
* 3. ็ฑๆฏ็ฉไปถๆ นๆๅๅ ฑ็ๅผ๏ผๆดๆฐ่ชๅทฑ็ @State๏ผ็จ parent.onPreferenceChange() ่็
*
* ๅญ็ฉไปถๅๅ ฑ --> PreferenceKey ่็ --> ๆฏ็ฉไปถๆดๆฐ
*/
// โญ๏ธ ๆๆ Text ็ๆๅคงๅฏฌๅบฆ
typealias MaxWidth = MaxValue<CGFloat> // ๐ฆ MaxValue<T: FloatingPoint>
// ๐
MyTextField
// -----------------------------------------------------------
// ไธปๅ่ฝ๏ผ
// 1. ๆๅ `MaxWidth` (PreferenceKey) ๅ ฑๅ่ชๅทฑ label ็ๅฏฌๅบฆใ
// 2. ่จญๅฎ่ชๅทฑ label ็ๅฏฌๅบฆ็บ `labelWidth` (โญ๏ธ ไพ่ช parent view)ใ
// (ๅ้ๅง็บ nil๏ผ่กจ็คบไธๅผทๅถ่จญๅฎๅฏฌๅบฆ๏ผ็ฑ label ่ชๅทฑๆฑบๅฎๅฏฌๅบฆ)
// 3. ๅช่ฆไพ่ช parent view ็ `labelWidth` ๆดๆฐไบ๏ผ่ชๅทฑๅฐฑๆ่ท่ๆดๆฐใ
// -----------------------------------------------------------
struct MyTextField: View {
@Binding var labelWidth: CGFloat? // label's width (โญ๏ธ ็ฑ parent @State ๆฑบๅฎ)
@Binding var text : String // text field's content (โญ๏ธ ๅไธ)
let label : String // text field's label
let placeholder: String // text field's placeholder
init(labelWidth: Binding<CGFloat?>, label: String, placeholder: String, text: Binding<String>) {
self._labelWidth = labelWidth
self._text = text
self.label = label
self.placeholder = placeholder
}
var body: some View {
HStack {
// label
Text(label)
// โญ๏ธ 1. ๅ MaxWidth ๅๅ ฑ่ชๅทฑ็ๅฏฌๅบฆ
.reportWidth(to: MaxWidth.self) // ๐View + pref, ๐ฆ MaxWidth
// โญ๏ธ 2. ๆฟๆฅไพ่ชๆฏ็ฉไปถ็ labelWidth (optional)๏ผไธฆ่จญๅฎๅฐบๅฏธ
// โญ๏ธ 3. ่จญๅฎ้ ๅณ
.frame(width: labelWidth, alignment: .trailing)
// text field
TextField(placeholder, text: $text)
.style(.rounded) // ๐TextField + style
}
}
}
// ๐
MyForm
struct MyForm: View {
@State var value1 = ""
@State var value2 = ""
@State var value3 = ""
// โญ๏ธ TextField ็็ตฑไธๅฏฌๅบฆ (ไธ้จๆฒ๏ผ็ฑๅญ่ฆไปถๅๅ ฑ๏ผMaxWidth ่็๏ผๆฏ่ฆไปถๆดๆฐ)
@State private var labelWidth: CGFloat?
var body: some View {
Form {
// ๐
MyTextField
MyTextField(
labelWidth : $labelWidth, // ๆฟๆฅไพ่ชๆฏ็ฉไปถ็ใ็ตฑไธๅฏฌๅบฆใ
label : "ๅงๅ",
placeholder: "่ซ่ผธๅ
ฅๆจ็ๅๅญ",
text : $value1
)
MyTextField(
labelWidth : $labelWidth,
label : "้ป่ฉฑ",
placeholder: "0900-000-123",
text : $value2
)
MyTextField(
labelWidth : $labelWidth,
label : "้ปๅญไฟก็ฎฑ",
placeholder: "email@company.com",
text : $value3
)
}// Form
.cornerRadius(12)
// โญ๏ธ 3. ๆฏ็ฉไปถๆ นๆๅๅ ฑ็ๅฏฌๅบฆ๏ผๆดๆฐ่ชๅทฑ็ labelWidth
.onPreferenceChange(MaxWidth.self) { self.labelWidth = $0 }
}
}
struct ContentView: View {
var body: some View {
VStack {
MyForm() // ๐
MyForm
Color.pink.cornerRadius(12)
}
.padding()
.background(Color.gray)
.cornerRadius(16)
.shadow(color: .black, radius: 6, x: 6, y: 6)
}
}
// live view
PlaygroundPage.current.setLiveView(ContentView())