Skip to content

Commit 3726b46

Browse files
committed
adapt macOS 26 SwiftUI behavior changes with minor tweaks
* Picker no longer occupies full width * Stepper becomes wide enough for UX, with minus button becoming ugly * Give padding top and bottom * Replace unnecessary Grid and ZStack with VStack * Unify and enlarge clickable area of +/-/up buttons on List
1 parent 6385a6d commit 3726b46

File tree

5 files changed

+57
-37
lines changed

5 files changed

+57
-37
lines changed

src/config/imageoptionview.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ struct ImageOptionView: OptionView {
1212
@ObservedObject var model: ImageOption
1313

1414
var body: some View {
15-
VStack {
15+
VStack(alignment: .leading) { // Avoid layout shift of Picker when switching modes.
1616
Picker("", selection: $model.mode) {
1717
ForEach(Array(modes.enumerated()), id: \.0) { idx, mode in
1818
Text(mode)

src/config/inputmethod.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ struct InputMethodConfigView: View {
135135
if let configModel = viewModel.configModel {
136136
VStack {
137137
let scrollView = ScrollView([.vertical]) {
138-
buildView(config: configModel).padding([.leading, .trailing])
138+
buildView(config: configModel).padding()
139139
}
140140
if #available(macOS 14.0, *) {
141141
scrollView.defaultScrollAnchor(.topTrailing)

src/config/navigationsplit.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,13 @@ struct ListConfigView: View {
2828
} detail: {
2929
if key == "theme" {
3030
TextField(NSLocalizedString("Type here to preview style", comment: ""), text: $dummyText)
31-
.padding()
31+
.padding([.top, .leading, .trailing])
3232
}
3333
ScrollView {
3434
if let config = viewModel.selectedConfig {
35-
buildView(config: config).padding([.leading, .trailing])
35+
buildView(config: config).padding()
3636
}
37-
}.padding([.top], 1)
37+
}.padding([.top], 1) // Cannot be 0 otherwise content overlaps with title bar.
3838
footer(
3939
reset: {
4040
// Reset only current page.

src/config/optionviews.swift

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct IntegerOptionView: OptionView {
9191
@FocusState private var isFocused: Bool
9292

9393
var body: some View {
94-
ZStack(alignment: .trailing) {
94+
HStack {
9595
TextField(label, value: $model.value, formatter: numberFormatter)
9696
.focused($isFocused)
9797
.textFieldStyle(RoundedBorderTextFieldStyle())
@@ -100,19 +100,30 @@ struct IntegerOptionView: OptionView {
100100
validate()
101101
}
102102
}
103-
.padding(.trailing, 60)
104-
HStack(spacing: 0) {
105-
Button {
103+
if #available(macOS 26.0, *) {
104+
Stepper {
105+
} onIncrement: {
106+
model.value += 1
107+
validate()
108+
} onDecrement: {
106109
model.value -= 1
107110
validate()
108-
} label: {
109-
Image(systemName: "minus")
110111
}
111-
Button {
112-
model.value += 1
113-
validate()
114-
} label: {
115-
Image(systemName: "plus")
112+
} else {
113+
// Stepper is too narrow.
114+
HStack(spacing: 0) {
115+
Button {
116+
model.value -= 1
117+
validate()
118+
} label: {
119+
Image(systemName: "minus")
120+
}
121+
Button {
122+
model.value += 1
123+
validate()
124+
} label: {
125+
Image(systemName: "plus")
126+
}
116127
}
117128
}
118129
}
@@ -241,8 +252,8 @@ struct ExternalOptionView: OptionView {
241252
.sheet(isPresented: $viewModel.hasConfig) {
242253
VStack {
243254
ScrollView([.vertical]) {
244-
buildView(config: viewModel.externalConfig!).padding([.leading, .trailing])
245-
}.padding([.top])
255+
buildView(config: viewModel.externalConfig!).padding()
256+
}
246257
footer(
247258
reset: {
248259
viewModel.externalConfig?.resetToDefault()
@@ -297,28 +308,34 @@ struct ListOptionView<T: Option & EmptyConstructible>: OptionView {
297308
AnyView(buildViewImpl(label: "", option: element.value))
298309

299310
let index = findElementIndex(element)
300-
Button(action: { moveUp(index: index) }) {
301-
Image(systemName: "arrow.up")
311+
Button {
312+
moveUp(index: index)
313+
} label: {
314+
Image(systemName: "arrow.up").square()
302315
}
303316
.disabled(index == 0)
304317
.buttonStyle(BorderlessButtonStyle())
305318

306-
Button(action: { remove(at: index) }) {
307-
Image(systemName: "minus")
308-
.frame(maxHeight: .infinity)
309-
.contentShape(Rectangle())
319+
Button {
320+
remove(at: index)
321+
} label: {
322+
Image(systemName: "minus").square()
310323
}
311324
.buttonStyle(BorderlessButtonStyle())
312325

313-
Button(action: { add(at: index) }) {
314-
Image(systemName: "plus")
326+
Button {
327+
add(at: index)
328+
} label: {
329+
Image(systemName: "plus").square()
315330
}
316331
.buttonStyle(BorderlessButtonStyle())
317332
}
318333
}
319334

320-
Button(action: { add(at: model.value.count) }) {
321-
Image(systemName: "plus")
335+
Button {
336+
add(at: model.value.count)
337+
} label: {
338+
Image(systemName: "plus").square()
322339
}
323340
.buttonStyle(BorderlessButtonStyle())
324341
.frame(maxWidth: .infinity, alignment: .trailing)
@@ -476,17 +493,16 @@ struct GroupOptionView: OptionView {
476493
let children: [Config]
477494

478495
var body: some View {
479-
Grid(alignment: .topLeading) {
496+
VStack(alignment: .leading, spacing: 8) {
480497
ForEach(children) { child in
481498
let subView = buildViewImpl(config: child)
482499
let subLabel = Text(subView.label)
483500
if subView is GroupOptionView {
484501
// If this is a nested group, put it inside a box, and let
485502
// it span two columns.
486-
GridRow {
503+
VStack(alignment: .leading, spacing: 4) {
487504
subLabel
488505
.font(.title3)
489-
.gridCellColumns(2)
490506
.help(NSLocalizedString("Right click to reset this group", comment: ""))
491507
.contextMenu {
492508
Button {
@@ -495,21 +511,19 @@ struct GroupOptionView: OptionView {
495511
Text("Reset to default")
496512
}
497513
}
498-
}
499-
GridRow {
500514
GroupBox {
501515
AnyView(subView)
502-
}.gridCellColumns(2)
516+
}
503517
}
504518
} else {
505519
// Otherwise, put the label in the left column and the
506520
// content in the right column.
507-
GridRow {
521+
HStack(spacing: 16) {
508522
if subView is ExternalOptionView {
509-
HStack {} // Label is baked in button.
523+
Spacer().frame(maxWidth: .infinity) // Label is baked in button.
510524
} else {
511525
subLabel
512-
.frame(minWidth: 100, maxWidth: 250, alignment: .trailing)
526+
.frame(maxWidth: .infinity, alignment: .trailing)
513527
.help(NSLocalizedString("Right click to reset this item", comment: ""))
514528
.contextMenu {
515529
Button {
@@ -520,6 +534,7 @@ struct GroupOptionView: OptionView {
520534
}
521535
}
522536
AnyView(subView)
537+
.frame(maxWidth: .infinity, alignment: .leading)
523538
}
524539
}
525540
}

src/config/ui.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ extension View {
1818
Image(systemName: "questionmark.circle.fill").help(text)
1919
}
2020
}
21+
22+
// Enlarge clickable area for border-less icon button, especially minus.
23+
func square() -> some View {
24+
self.frame(width: 20, height: 20).background(Color.black.opacity(0.001))
25+
}
2126
}
2227

2328
func footer(reset: @escaping () -> Void, apply: @escaping () -> Void, close: @escaping () -> Void)

0 commit comments

Comments
 (0)