Skip to content

Commit 03501b1

Browse files
mbrandonwmluisbrown
authored andcommitted
Fix for attribute cycle in tic-tac-toe. (#1114)
* Fix for attribute cytcle. * wip * wip
1 parent bfa446b commit 03501b1

File tree

4 files changed

+107
-107
lines changed

4 files changed

+107
-107
lines changed

Examples/TicTacToe/App/RootView.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ struct RootView: View {
4343
var body: some View {
4444
NavigationView {
4545
Form {
46-
Section(
47-
header: Text(readMe).padding([.bottom], 16)
48-
) {
46+
Text(readMe)
47+
48+
Section {
4949
Button("SwiftUI version") { self.showGame = .swiftui }
5050
Button("UIKit version") { self.showGame = .uikit }
5151
}

Examples/TicTacToe/tic-tac-toe/Sources/LoginSwiftUI/LoginView.swift

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -43,59 +43,59 @@ public struct LoginView: View {
4343

4444
public var body: some View {
4545
WithViewStore(self.store.scope(state: ViewState.init, action: LoginAction.init)) { viewStore in
46-
ScrollView {
47-
VStack(spacing: 16) {
48-
Text(
46+
Form {
47+
Text(
4948
"""
5049
To login use any email and "password" for the password. If your email contains the \
5150
characters "2fa" you will be taken to a two-factor flow, and on that screen you can \
5251
use "1234" for the code.
5352
"""
54-
)
55-
56-
VStack(alignment: .leading) {
57-
Text("Email")
58-
TextField(
59-
60-
text: viewStore.binding(get: \.email, send: ViewAction.emailChanged)
61-
)
62-
.autocapitalization(.none)
63-
.keyboardType(.emailAddress)
64-
.textContentType(.emailAddress)
65-
.textFieldStyle(.roundedBorder)
66-
}
53+
)
6754

68-
VStack(alignment: .leading) {
69-
Text("Password")
70-
SecureField(
71-
"••••••••",
72-
text: viewStore.binding(get: \.password, send: ViewAction.passwordChanged)
73-
)
74-
.textFieldStyle(.roundedBorder)
75-
}
55+
Section {
56+
TextField(
57+
58+
text: viewStore.binding(get: \.email, send: ViewAction.emailChanged)
59+
)
60+
.autocapitalization(.none)
61+
.keyboardType(.emailAddress)
62+
.textContentType(.emailAddress)
7663

77-
NavigationLink(
78-
destination: IfLetStore(
79-
self.store.scope(state: \.twoFactor, action: LoginAction.twoFactor),
80-
then: TwoFactorView.init(store:)
81-
),
82-
isActive: viewStore.binding(
83-
get: \.isTwoFactorActive,
84-
send: { $0 ? .loginButtonTapped : .twoFactorDismissed }
85-
)
86-
) {
87-
Text("Log in")
64+
SecureField(
65+
"••••••••",
66+
text: viewStore.binding(get: \.password, send: ViewAction.passwordChanged)
67+
)
68+
}
8869

89-
if viewStore.isActivityIndicatorVisible {
90-
ProgressView()
70+
NavigationLink(
71+
destination: IfLetStore(
72+
self.store.scope(state: \.twoFactor, action: LoginAction.twoFactor),
73+
then: TwoFactorView.init(store:)
74+
),
75+
isActive: viewStore.binding(
76+
get: \.isTwoFactorActive,
77+
send: {
78+
// NB: SwiftUI will print errors to the console about "AttributeGraph: cycle detected"
79+
// if you disable a text field while it is focused. This hack will force all
80+
// fields to unfocus before we send the action to the view store.
81+
// CF: https://stackoverflow.com/a/69653555
82+
UIApplication.shared.sendAction(
83+
#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil
84+
)
85+
return $0 ? .loginButtonTapped : .twoFactorDismissed
9186
}
87+
)
88+
) {
89+
Text("Log in")
90+
if viewStore.isActivityIndicatorVisible {
91+
Spacer()
92+
ProgressView()
9293
}
93-
.disabled(viewStore.isLoginButtonDisabled)
9494
}
95-
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
96-
.disabled(viewStore.isFormDisabled)
97-
.padding(.horizontal)
95+
.disabled(viewStore.isLoginButtonDisabled)
9896
}
97+
.disabled(viewStore.isFormDisabled)
98+
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
9999
}
100100
.navigationBarTitle("Login")
101101
}

Examples/TicTacToe/tic-tac-toe/Sources/NewGameSwiftUI/NewGameView.swift

Lines changed: 37 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -36,50 +36,47 @@ public struct NewGameView: View {
3636
public var body: some View {
3737
WithViewStore(self.store.scope(state: ViewState.init, action: NewGameAction.init)) {
3838
viewStore in
39-
ScrollView {
40-
VStack(spacing: 16) {
41-
VStack(alignment: .leading) {
42-
Text("X Player Name")
43-
TextField(
44-
"Blob Sr.",
45-
text: viewStore.binding(get: \.xPlayerName, send: ViewAction.xPlayerNameChanged)
46-
)
47-
.autocapitalization(.words)
48-
.disableAutocorrection(true)
49-
.textContentType(.name)
50-
.textFieldStyle(.roundedBorder)
51-
}
39+
Form {
40+
Section {
41+
TextField(
42+
"Blob Sr.",
43+
text: viewStore.binding(get: \.xPlayerName, send: ViewAction.xPlayerNameChanged)
44+
)
45+
.autocapitalization(.words)
46+
.disableAutocorrection(true)
47+
.textContentType(.name)
48+
} header: {
49+
Text("X Player Name")
50+
}
5251

53-
VStack(alignment: .leading) {
54-
Text("O Player Name")
55-
TextField(
56-
"Blob Jr.",
57-
text: viewStore.binding(get: \.oPlayerName, send: ViewAction.oPlayerNameChanged)
58-
)
59-
.autocapitalization(.words)
60-
.disableAutocorrection(true)
61-
.textContentType(.name)
62-
.textFieldStyle(.roundedBorder)
63-
}
52+
Section {
53+
TextField(
54+
"Blob Jr.",
55+
text: viewStore.binding(get: \.oPlayerName, send: ViewAction.oPlayerNameChanged)
56+
)
57+
.autocapitalization(.words)
58+
.disableAutocorrection(true)
59+
.textContentType(.name)
60+
} header: {
61+
Text("O Player Name")
62+
}
6463

65-
NavigationLink(
66-
destination: IfLetStore(
67-
self.store.scope(state: \.game, action: NewGameAction.game),
68-
then: GameView.init(store:)
69-
),
70-
isActive: viewStore.binding(
71-
get: \.isGameActive,
72-
send: { $0 ? .letsPlayButtonTapped : .gameDismissed }
73-
)
74-
) {
75-
Text("Let's play!")
76-
}
77-
.disabled(viewStore.isLetsPlayButtonDisabled)
64+
NavigationLink(
65+
destination: IfLetStore(
66+
self.store.scope(state: \.game, action: NewGameAction.game),
67+
then: GameView.init(store:)
68+
),
69+
isActive: viewStore.binding(
70+
get: \.isGameActive,
71+
send: { $0 ? .letsPlayButtonTapped : .gameDismissed }
72+
)
73+
) {
74+
Text("Let's play!")
7875
}
79-
.padding(.horizontal)
76+
.disabled(viewStore.isLetsPlayButtonDisabled)
77+
.navigationBarTitle("New Game")
78+
.navigationBarItems(trailing: Button("Logout") { viewStore.send(.logoutButtonTapped) })
8079
}
81-
.navigationBarTitle("New Game")
82-
.navigationBarItems(trailing: Button("Logout") { viewStore.send(.logoutButtonTapped) })
8380
}
8481
}
8582
}

Examples/TicTacToe/tic-tac-toe/Sources/TwoFactorSwiftUI/TwoFactorView.swift

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -37,37 +37,40 @@ public struct TwoFactorView: View {
3737
WithViewStore(
3838
self.store.scope(state: ViewState.init, action: TwoFactorAction.init)
3939
) { viewStore in
40-
ScrollView {
41-
VStack(spacing: 16) {
42-
Text(#"To confirm the second factor enter "1234" into the form."#)
40+
Form {
41+
Text(#"To confirm the second factor enter "1234" into the form."#)
4342

44-
VStack(alignment: .leading) {
45-
Text("Code")
46-
TextField(
47-
"1234",
48-
text: viewStore.binding(get: \.code, send: ViewAction.codeChanged)
43+
Section {
44+
TextField(
45+
"1234",
46+
text: viewStore.binding(get: \.code, send: ViewAction.codeChanged)
47+
)
48+
.keyboardType(.numberPad)
49+
}
50+
51+
HStack {
52+
Button("Submit") {
53+
// NB: SwiftUI will print errors to the console about "AttributeGraph: cycle detected"
54+
// if you disable a text field while it is focused. This hack will force all
55+
// fields to unfocus before we send the action to the view store.
56+
// CF: https://stackoverflow.com/a/69653555
57+
UIApplication.shared.sendAction(
58+
#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil
4959
)
50-
.keyboardType(.numberPad)
51-
.textFieldStyle(.roundedBorder)
60+
viewStore.send(.submitButtonTapped)
5261
}
62+
.disabled(viewStore.isSubmitButtonDisabled)
5363

54-
HStack {
55-
Button("Submit") {
56-
viewStore.send(.submitButtonTapped)
57-
}
58-
.disabled(viewStore.isSubmitButtonDisabled)
59-
60-
if viewStore.isActivityIndicatorVisible {
61-
ProgressView()
62-
}
64+
if viewStore.isActivityIndicatorVisible {
65+
Spacer()
66+
ProgressView()
6367
}
6468
}
65-
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
66-
.disabled(viewStore.isFormDisabled)
67-
.padding(.horizontal)
6869
}
70+
.alert(self.store.scope(state: \.alert), dismiss: .alertDismissed)
71+
.disabled(viewStore.isFormDisabled)
72+
.navigationBarTitle("Confirmation Code")
6973
}
70-
.navigationBarTitle("Confirmation Code")
7174
}
7275
}
7376

0 commit comments

Comments
 (0)