3
\$\begingroup\$

I want to understand if I got this concept correctly.

  1. I should start work from ViewState; a reference type object holding the state of a view. Here it is called PageIndicatorVM. It is bad-to-impossible to have any internal state in a View.
  2. No logic in the View or so they say. Yet here and there in the code samples I see ternary operators and loops.
  3. @Bidindg property wrapper seems quite dangerous for me. It breaks unidirectional data flow and I have doubts if I used it correctly here.

Please point out my mistakes. Will appreciate if you point me to best practices.


This is a dot indicator component. It switches when vm.next() is called or user taps on a dot.

struct DotIndicator: View {
 let pageIndex: Int
 
 @Binding var isOn: Int
 
 var body: some View {
 
 Button(action: {
 self.isOn = self.pageIndex
 }) {
 Circle()
 .scaleEffect( isOn == pageIndex ? 1.3 : 0.9)
 .animation(.spring())
 }
 
 }
}
class PageIndicatorVM: ObservableObject {
 @Published var currentPage: Int
 
 let numPages: Int
 
 init(numPages: Int, currentPage: Int = 0) {
 self.numPages = numPages
 self.currentPage = currentPage
 }
 
 func next() {
 currentPage = (currentPage + 1) % numPages
 }
}
struct PageIndicator: View {
 @ObservedObject private var vm = PageIndicatorVM(numPages: 5)
 
 private let spacing: CGFloat = 2
 private let dotSize: CGFloat = 8
 
 var body: some View {
 VStack {
 HStack(alignment: .center, spacing: spacing) {
 ForEach((0..<vm.numPages ), id: \.self) {
 DotIndicator(pageIndex: 0,ドル isOn: self.$vm.currentPage)
 .frame(
 width: self.dotSize,
 height: self.dotSize
 )
 }
 }
 }
 }
}
Peilonrayz
44.4k7 gold badges80 silver badges157 bronze badges
asked Jun 25, 2020 at 18:45
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

Your code is perfectly fine. Just a couple of comments:

It is bad-to-impossible to have any internal state in a View.

Actually SwiftUI was designed to handle state changes directly in a view. For simple cases you can easily do:

struct PageIndicator: View {
 @State private var currentPage = 0
 let numPages: Int = 5
 private let spacing: CGFloat = 2
 private let dotSize: CGFloat = 8
 var body: some View {
 VStack {
 HStack(alignment: .center, spacing: spacing) {
 ForEach(0 ..< numPages, id: \.self) {
 DotIndicator(pageIndex: 0,ドル isOn: self.$currentPage)
 .frame(width: self.dotSize, height: self.dotSize)
 }
 }
 }
 }
}
PageIndicator(numPages: 5)

What's more sometimes an @ObservedObject may be reinitialised contrary to a @State property (unless you use a @StateObject available in SwiftUI 2.0).

For more information see: What is the difference between ObservedObject and StateObject in SwiftUI.

No logic in the View or so they say. Yet here and there in the code samples I see ternary operators and loops.

True, but for simple cases creating a whole new ViewModel can actually make code less readable (as in the example above). Note that a ForEach loop is perfectly valid in a view.


Apart from the comments above you can use an Image(systemName:) instead of a Circle:

struct DotIndicator: View {
 let pageIndex: Int
 @Binding var isOn: Int
 var body: some View {
 Button(action: {
 self.isOn = self.pageIndex
 }) {
 Image(systemName: "circle.fill")
 .imageScale(.small)
 .scaleEffect(isOn == pageIndex ? 1.0 : 0.7)
 .animation(.spring())
 }
 }
}

Because a Circle consumes all available space, you need to constrain it with the .frame modifier. You don't have to do that with an Image - it reduces the code (dotSize is no longer needed) and scales automatically.

answered Sep 2, 2020 at 20:07
\$\endgroup\$
1
  • \$\begingroup\$ Thank You for the review! I had been working with SwiftUI for a while after this post and now i am comfortable with it. Agree with all your comments! \$\endgroup\$ Commented Sep 3, 2020 at 7:16

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.