Home About Contact

 

How to use function that returns some View to increase SwiftUI code readability

3 min read

To have better readability for my SwiftUI code, I’ll always move some complex parts in my SwiftUI code to some dedicated functions. Especially those parts that need to display different views on different conditions.

Such as the followings, the label of the Button will show different View, dependent on the value of the @State variable randomNumber if it’s divisible by 2; which is set with random value of 1 to 10 when the button is tapped:

struct RandomNumberView {
   @State private var randomNumber : Int = 0

   var body: some View {
     Button(action: {
          self.randomNumber = Int.random(in: (1...10))
      },label : {

          if ( self.randomNumber % 2 == 1 ){

             ZStack{
                Circle().fill(Color.red)
                .frame(width:200,height:200)
                Circle().fill(Color.orange)
                .frame(width:150,height:150)
                Text("You've won!")
                .foregroundColor(.white)
                .font(.headline)
             }
          }
          else {
            Text("Try your luck again!")
            .font(.headline)
          }
    })
}

In the above, the code inside label closure of the Button is to show a ZStack of views if the @State variable randomNumber is not divisible by 2 else will show a Text view.

It is coded inline, making the code rather messy or hard to read. So for better readability is to move that part of the code to a dedicated function which returns some View as follows:

extension RandomNumberView {
    @ViewBuilder private func createButtonLabel() -> some View {

        if ( self.randomNumber % 2 == 1 ){
           ZStack{
               Circle().fill(Color.red)
               .frame(width:200,height:200)
               Circle().fill(Color.orange)
               .frame(width:150,height:150)
               Text("You've won!")
               .foregroundColor(.white)
               .font(.headline)
           }
        }
        else {
           Text("Try your luck again!")
           .font(.headline)
        }
    }
}

So with the above extension function, we can just call the function createButtonLabel() in the closure of the Button’s label, and the code has much better readability as follows:

var body: some View {
   Button(action: {
      self.randomNumber = Int.random(in: (1...10))
   }, label : {
      createButtonLabel()
   })    
}

The result:

Please note that we apply the @ViewBuilder attribute to the function createButtonLabel(). Besides the @ViewBuilder attribute, there are two other ways that can do the same:

1. Use a type-erased wrapper, AnyView to wrap the View and return AnyView as follows:

private func createButtonLabel() -> AnyView {
  if ( self.randomNumber % 2 == 1 ){
     return AnyView(
        ZStack{
           Circle().fill(Color.red)
           .frame(width:200,height:200)
                  
           Circle().fill(Color.orange)
           .frame(width:150,height:150)
                 
           Text("You've won!")
           .foregroundColor(.white)
           .font(.headline)
        }
     )
  }
  else {   
      return AnyView (
        Text("Try your luck again!")
       .font(.headline) 
      )
  }
}

But using AnyView to wrap stacks of Views or a View with some styling, doesn’t make the code to look good either.

2. Another way of doing it is using Group, a transparent container, to wrap the views as follows and you don’t need the @ViewBuilder attribute for the function :

private func createButtonLabel() -> some View { 
  Group {
     if ( self.randomNumber % 2 == 1 ){

        ZStack{
           Circle().fill(Color.red)
           .frame(width:200,height:200)
           Circle().fill(Color.orange)
           .frame(width:150,height:150)
           Text("You've won!")
           .foregroundColor(.white)
           .font(.headline)
        }
     }
     else {
        Text("Try your luck again!")
        .font(.headline)
     }
  }
}

Both the three methods produce the same result for our requirement here with just a simple layout. But if you have adjacent views around the Button, the adjacent views will be displaced when the label of the Button is being changed from the Text view to the ZStack of views.

For example as follows, we have a VStack which displays the Button with a Text view below it :

var body: some View {
    VStack(spacing: 20 ) {
        
      Button(action: {
         self.randomNumber = Int.random(in: (1...10))
      }, label : {
         createButtonLabel()
      })
            
      Text("This is a little fun game which you just need to 
      press the above button to try your luck!")
      .frame(width: 240, height: 100)           
   }
}

When it’s run, the result will be like below, showing the bottom Text view displaced up and down when the label of the Button is being changed.

In order to fix that, we added the frame modifier for the Group to fix its size in the createButtonLabel() function (line 21), such as follows:

private func createButtonLabel() -> some View { 
  Group {
     if ( self.randomNumber % 2 == 1 ){

        ZStack{
           Circle().fill(Color.red)
           .frame(width:200,height:200)
           Circle().fill(Color.orange)
           .frame(width:150,height:150)
           Text("You've won!")
           .foregroundColor(.white)
           .font(.headline)
        }
     }
     else {
        Text("Try your luck again!")
        .font(.headline)
     }
  }
  .frame(width: 200, height: 200) // add the frame modifier to fix its size
}

So, the bottom Text view will not be displaced up and down as below:

That’s all for now. Thanks for reading and happy learning!

Spread the love
Posted on January 11, 2021 By Christopher Chee

3 thoughts on “How to use function that returns some View to increase SwiftUI code readability”

Please leave us your comments below, if you find any errors or mistakes with this post. Or you have better idea to suggest for better result etc.


Our FB Twitter Our IG Copyright © 2024