Skip to content

Commit ade6ab7

Browse files
authored
Merge pull request #6625 from woocommerce/issue/6018-fields-alignment-in-address-form
Order Creation: Align text fields in the address form
2 parents 34998d9 + 2d99da8 commit ade6ab7

File tree

5 files changed

+118
-5
lines changed

5 files changed

+118
-5
lines changed

WooCommerce/Classes/ViewRelated/Orders/Order Details/Address Edit/EditOrderAddressForm.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,31 +194,51 @@ struct SingleAddressForm: View {
194194
///
195195
@State private var showStateSelector = false
196196

197+
/// Stores shared value derived from max title width among all the fields.
198+
///
199+
@State private var titleWidth: CGFloat? = nil
200+
197201
var body: some View {
202+
content
203+
.onPreferenceChange(MaxWidthPreferenceKey.self) { value in
204+
if let value = value {
205+
titleWidth = value
206+
}
207+
}
208+
}
209+
210+
@ViewBuilder
211+
var content: some View {
198212
ListHeaderView(text: Localization.detailsSection, alignment: .left)
199213
.padding(.horizontal, insets: safeAreaInsets)
200214
.accessibility(addTraits: .isHeader)
201215
VStack(spacing: 0) {
202216
TitleAndTextFieldRow(title: Localization.firstNameField,
217+
titleWidth: $titleWidth,
203218
placeholder: "",
204219
text: $fields.firstName,
205220
symbol: nil,
221+
fieldAlignment: .leading,
206222
keyboardType: .default)
207223
Divider()
208224
.padding(.leading, Constants.dividerPadding)
209225
TitleAndTextFieldRow(title: Localization.lastNameField,
226+
titleWidth: $titleWidth,
210227
placeholder: "",
211228
text: $fields.lastName,
212229
symbol: nil,
230+
fieldAlignment: .leading,
213231
keyboardType: .default)
214232
Divider()
215233
.padding(.leading, Constants.dividerPadding)
216234

217235
if showEmailField {
218236
TitleAndTextFieldRow(title: Localization.emailField,
237+
titleWidth: $titleWidth,
219238
placeholder: "",
220239
text: $fields.email,
221240
symbol: nil,
241+
fieldAlignment: .leading,
222242
keyboardType: .emailAddress)
223243
.autocapitalization(.none)
224244
Divider()
@@ -227,9 +247,11 @@ struct SingleAddressForm: View {
227247
}
228248

229249
TitleAndTextFieldRow(title: Localization.phoneField,
250+
titleWidth: $titleWidth,
230251
placeholder: "",
231252
text: $fields.phone,
232253
symbol: nil,
254+
fieldAlignment: .leading,
233255
keyboardType: .phonePad)
234256
}
235257
.padding(.horizontal, insets: safeAreaInsets)
@@ -242,37 +264,47 @@ struct SingleAddressForm: View {
242264
VStack(spacing: 0) {
243265
Group {
244266
TitleAndTextFieldRow(title: Localization.companyField,
267+
titleWidth: $titleWidth,
245268
placeholder: Localization.placeholderOptional,
246269
text: $fields.company,
247270
symbol: nil,
271+
fieldAlignment: .leading,
248272
keyboardType: .default)
249273
Divider()
250274
.padding(.leading, Constants.dividerPadding)
251275
TitleAndTextFieldRow(title: Localization.address1Field,
276+
titleWidth: $titleWidth,
252277
placeholder: "",
253278
text: $fields.address1,
254279
symbol: nil,
280+
fieldAlignment: .leading,
255281
keyboardType: .default)
256282
Divider()
257283
.padding(.leading, Constants.dividerPadding)
258284
TitleAndTextFieldRow(title: Localization.address2Field,
285+
titleWidth: $titleWidth,
259286
placeholder: Localization.placeholderOptional,
260287
text: $fields.address2,
261288
symbol: nil,
289+
fieldAlignment: .leading,
262290
keyboardType: .default)
263291
Divider()
264292
.padding(.leading, Constants.dividerPadding)
265293
TitleAndTextFieldRow(title: Localization.cityField,
294+
titleWidth: $titleWidth,
266295
placeholder: "",
267296
text: $fields.city,
268297
symbol: nil,
298+
fieldAlignment: .leading,
269299
keyboardType: .default)
270300
Divider()
271301
.padding(.leading, Constants.dividerPadding)
272302
TitleAndTextFieldRow(title: Localization.postcodeField,
303+
titleWidth: $titleWidth,
273304
placeholder: "",
274305
text: $fields.postcode,
275306
symbol: nil,
307+
fieldAlignment: .leading,
276308
keyboardType: .default)
277309
Divider()
278310
.padding(.leading, Constants.dividerPadding)
@@ -301,7 +333,9 @@ struct SingleAddressForm: View {
301333
}
302334

303335
TitleAndValueRow(title: Localization.countryField,
336+
titleWidth: $titleWidth,
304337
value: .init(placeHolder: Localization.placeholderSelectOption, content: fields.country),
338+
valueTextAlignment: .leading,
305339
selectionStyle: .disclosure) {
306340
showCountrySelector = true
307341
}
@@ -320,15 +354,19 @@ struct SingleAddressForm: View {
320354
@ViewBuilder private func stateRow() -> some View {
321355
if showStateFieldAsSelector {
322356
TitleAndValueRow(title: Localization.stateField,
357+
titleWidth: $titleWidth,
323358
value: .init(placeHolder: Localization.placeholderSelectOption, content: fields.state),
359+
valueTextAlignment: .leading,
324360
selectionStyle: .disclosure) {
325361
showStateSelector = true
326362
}
327363
} else {
328364
TitleAndTextFieldRow(title: Localization.stateField,
365+
titleWidth: $titleWidth,
329366
placeholder: "",
330367
text: $fields.state,
331368
symbol: nil,
369+
fieldAlignment: .leading,
332370
keyboardType: .default)
333371
}
334372
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import SwiftUI
2+
3+
/// PreferenceKey to store max value.
4+
/// Used in `MaxWidthModifier` to calculate max title width among multiple fields/rows.
5+
///
6+
struct MaxWidthPreferenceKey: PreferenceKey {
7+
static var defaultValue: CGFloat? = nil
8+
9+
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
10+
if let nv = nextValue(), nv > value ?? .zero {
11+
value = nv
12+
}
13+
}
14+
}
15+
16+
/// Modifier to calculate view width and store it in `MaxWidthPreferenceKey`.
17+
/// Used to calculate max frame and then align multiple fields/rows.
18+
///
19+
struct MaxWidthModifier: ViewModifier {
20+
private var sizeView: some View {
21+
GeometryReader { geometry in
22+
Color.clear
23+
.preference(key: MaxWidthPreferenceKey.self,
24+
value: geometry.size.width)
25+
}
26+
}
27+
28+
func body(content: Content) -> some View {
29+
content.background(sizeView)
30+
}
31+
}

WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TitleAndTextFieldRow.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ struct TitleAndTextFieldRow: View {
1313

1414
@Binding private var text: String
1515

16+
/// Static width for title label. Used to align values between different rows.
17+
/// If `nil` - title will have intrinsic size.
18+
///
19+
@Binding private var titleWidth: CGFloat?
20+
1621
init(title: String,
22+
titleWidth: Binding<CGFloat?> = .constant(nil),
1723
placeholder: String,
1824
text: Binding<String>,
1925
symbol: String? = nil,
@@ -22,6 +28,7 @@ struct TitleAndTextFieldRow: View {
2228
keyboardType: UIKeyboardType = .default,
2329
onEditingChanged: ((Bool) -> Void)? = nil) {
2430
self.title = title
31+
self._titleWidth = titleWidth
2532
self.placeholder = placeholder
2633
self._text = text
2734
self.symbol = symbol
@@ -32,11 +39,13 @@ struct TitleAndTextFieldRow: View {
3239
}
3340

3441
var body: some View {
35-
AdaptiveStack(horizontalAlignment: .leading) {
42+
AdaptiveStack(horizontalAlignment: .leading, spacing: Constants.spacing) {
3643
Text(title)
3744
.bodyStyle()
3845
.lineLimit(1)
3946
.fixedSize()
47+
.modifier(MaxWidthModifier())
48+
.frame(width: titleWidth, alignment: .leading)
4049
HStack {
4150
TextField(placeholder, text: $text, onEditingChanged: onEditingChanged ?? { _ in })
4251
.multilineTextAlignment(fieldAlignment)
@@ -58,6 +67,7 @@ private extension TitleAndTextFieldRow {
5867
enum Constants {
5968
static let height: CGFloat = 44
6069
static let padding: CGFloat = 16
70+
static let spacing: CGFloat = 20
6171
}
6272
}
6373

WooCommerce/Classes/ViewRelated/ReusableViews/SwiftUI Components/TitleAndValueRow.swift

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,40 @@ struct TitleAndValueRow: View {
1212

1313
private let title: String
1414
private let value: Value
15+
private let valueTextAlignment: TextAlignment
1516
private let bold: Bool
1617
private let selectionStyle: SelectionStyle
1718
private let action: () -> Void
1819

19-
init(title: String, value: Value, bold: Bool = false, selectionStyle: SelectionStyle = .none, action: @escaping () -> Void = {}) {
20+
/// Static width for title label. Used to align values between different rows.
21+
/// If `nil` - title will have intrinsic size.
22+
///
23+
@Binding private var titleWidth: CGFloat?
24+
25+
/// Value frame alignment derived from its text alignment
26+
///
27+
private var valueFrameAlignment: Alignment {
28+
switch valueTextAlignment {
29+
case .trailing:
30+
return .trailing
31+
case .leading:
32+
return .leading
33+
default:
34+
return .center
35+
}
36+
}
37+
38+
init(title: String,
39+
titleWidth: Binding<CGFloat?> = .constant(nil),
40+
value: Value,
41+
valueTextAlignment: TextAlignment = .trailing,
42+
bold: Bool = false,
43+
selectionStyle: SelectionStyle = .none,
44+
action: @escaping () -> Void = {}) {
2045
self.title = title
46+
self._titleWidth = titleWidth
2147
self.value = value
48+
self.valueTextAlignment = valueTextAlignment
2249
self.bold = bold
2350
self.selectionStyle = selectionStyle
2451
self.action = action
@@ -29,15 +56,17 @@ struct TitleAndValueRow: View {
2956
action()
3057
}, label: {
3158
HStack {
32-
AdaptiveStack(horizontalAlignment: .leading) {
59+
AdaptiveStack(horizontalAlignment: .leading, spacing: Constants.spacing) {
3360
Text(title)
3461
.style(bold: bold, highlighted: selectionStyle == .highlight)
3562
.multilineTextAlignment(.leading)
63+
.modifier(MaxWidthModifier())
64+
.frame(width: titleWidth, alignment: .leading)
3665

3766
Text(value.text)
3867
.style(for: value, bold: bold, highlighted: false)
39-
.multilineTextAlignment(.trailing)
40-
.frame(maxWidth: .infinity, alignment: .trailing)
68+
.multilineTextAlignment(valueTextAlignment)
69+
.frame(maxWidth: .infinity, alignment: valueFrameAlignment)
4170
.padding(.vertical, Constants.verticalPadding)
4271
}
4372

@@ -108,6 +137,7 @@ private extension TitleAndValueRow {
108137
static let maxHeight: CGFloat = 136
109138
static let horizontalPadding: CGFloat = 16
110139
static let verticalPadding: CGFloat = 12
140+
static let spacing: CGFloat = 20
111141
}
112142
}
113143

WooCommerce/WooCommerce.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@
10411041
AE7C957B27C3D5DA007E8E12 /* FeeLineDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7C957A27C3D5DA007E8E12 /* FeeLineDetails.swift */; };
10421042
AE7C957D27C3F187007E8E12 /* FeeLineDetailsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7C957C27C3F187007E8E12 /* FeeLineDetailsViewModel.swift */; };
10431043
AE7C957F27C417FA007E8E12 /* FeeLineDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE7C957E27C417FA007E8E12 /* FeeLineDetailsViewModelTests.swift */; };
1044+
AE8AEA8628084EC90054BDA2 /* MaxWidthPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8AEA8528084EC90054BDA2 /* MaxWidthPreference.swift */; };
10441045
AE90475C27A99D6000073E1D /* CreateOrderAddressFormViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE90475B27A99D6000073E1D /* CreateOrderAddressFormViewModelTests.swift */; };
10451046
AE9E04752776213E003FA09E /* OrderCustomerSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE9E04742776213E003FA09E /* OrderCustomerSection.swift */; };
10461047
AEA3F90D27BE76B300B9F555 /* ShippingLineDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEA3F90C27BE76B300B9F555 /* ShippingLineDetails.swift */; };
@@ -2726,6 +2727,7 @@
27262727
AE7C957A27C3D5DA007E8E12 /* FeeLineDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeLineDetails.swift; sourceTree = "<group>"; };
27272728
AE7C957C27C3F187007E8E12 /* FeeLineDetailsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeLineDetailsViewModel.swift; sourceTree = "<group>"; };
27282729
AE7C957E27C417FA007E8E12 /* FeeLineDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeeLineDetailsViewModelTests.swift; sourceTree = "<group>"; };
2730+
AE8AEA8528084EC90054BDA2 /* MaxWidthPreference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MaxWidthPreference.swift; sourceTree = "<group>"; };
27292731
AE90475B27A99D6000073E1D /* CreateOrderAddressFormViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateOrderAddressFormViewModelTests.swift; sourceTree = "<group>"; };
27302732
AE9E04742776213E003FA09E /* OrderCustomerSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderCustomerSection.swift; sourceTree = "<group>"; };
27312733
AEA3F90C27BE76B300B9F555 /* ShippingLineDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShippingLineDetails.swift; sourceTree = "<group>"; };
@@ -5300,6 +5302,7 @@
53005302
45DB703F261209B10064A6CF /* ItemToFulfillRow.swift */,
53015303
45DB704926121F3C0064A6CF /* TitleAndValueRow.swift */,
53025304
45DB705926124C710064A6CF /* TitleAndTextFieldRow.swift */,
5305+
AE8AEA8528084EC90054BDA2 /* MaxWidthPreference.swift */,
53035306
CC4A4FF026557D0E00B75DCD /* TitleAndToggleRow.swift */,
53045307
DE77889726FCA39B008DFF44 /* TitleAndSubtitleRow.swift */,
53055308
4521396D27FEE55200964ED3 /* FullScreenTextView.swift */,
@@ -9353,6 +9356,7 @@
93539356
4541D88A270718F6005A9E30 /* ShippingLabelCarriersSectionViewModel.swift in Sources */,
93549357
77E53EBF2510C153003D385F /* ProductDownloadListViewModel.swift in Sources */,
93559358
B910686027F1F28F00AD0575 /* GhostableViewController.swift in Sources */,
9359+
AE8AEA8628084EC90054BDA2 /* MaxWidthPreference.swift in Sources */,
93569360
DE19BB1A26C3B5DC00AB70D9 /* ShippingLabelCustomsFormItemDetailsViewModel.swift in Sources */,
93579361
2667BFE52530DCF4008099D4 /* RefundItemsValuesCalculationUseCase.swift in Sources */,
93589362
B55BC1F121A878A30011A0C0 /* String+HTML.swift in Sources */,

0 commit comments

Comments
 (0)