Home > Software engineering >  where to place a function in SwiftUI
where to place a function in SwiftUI

Time:11-24

I am trying to write a function that puts a rectangle on the screen in a pre-existing HStack. This is the code without the function (you can see that there is some code repetition used put a few rectangles in the HStack):

struct ContentView: View {
    @State var backgroundHeight = 60.0
    @State var backgroundWidth = 60.0
    @State var backgroundCorners = 10.0
    
    @State var highlightHeight = 8.0
    @State var highlightWidth = 8.0
    @State var highlightCorners = 3.0
    
    var body: some View {
        Color.blue
            .frame(width:backgroundWidth, height:backgroundHeight)
            .cornerRadius(backgroundCorners)
            .overlay(alignment:.center){
                HStack(spacing: 2){
                    Rectangle()
                        .foregroundColor(.yellow)
                        .frame(width:highlightWidth, height:highlightHeight)
                        .cornerRadius(highlightCorners)
                    Rectangle()
                        .foregroundColor(.cyan)
                        .frame(width:highlightWidth, height:highlightHeight)
                        .cornerRadius(highlightCorners)
                    Rectangle()
                        .foregroundColor(.red)
                        .frame(width:highlightWidth, height:highlightHeight)
                        .cornerRadius(highlightCorners)
                    Rectangle()
                        .foregroundColor(.white)
                        .frame(width:highlightWidth, height:highlightHeight)
                        .cornerRadius(highlightCorners)
                }
        }
    }
}

This text places a small rectangle on the screen with some smaller rectangles overlayed.

I then tried using the following function to streamline the code (and then calling the function in the HStack):

func quickHighlight {
Rectangle()
    .foregroundColor(.yellow)
    .frame(width: highlightWidth, height: highlightHeight)
    .cornerRadius(highlightCorners)
}

I tried putting a variety of permutations and putting it in different parts both in and out of the code. Although the function seems to generate error messages depending on where it is placed such as 'Cannot infer contextual base...' to 'Closure containing a declaration cannot be used with result builder'. The puzzling thing is the very basic function I used as a contextual basis for this learning exercise seemed to indicate this should work (although I am sure there is something overlooked).

FYI my goal was to try a case statement with the function where the function receives an integer and then iterates through a few options to assign a colour to the rectangle.

Any help greatly appreciated.

CodePudding user response:

The standard way is to make a subview. In SwiftUI small views increases performance because it tightens invalidation, i.e. it only needs to recompute the body funcs where the lets/vars have actually changed. Don't use a func that takes params to return a View because that breaks SwiftUI's change detection. A view modifier is an interesting way to make it even more reusable, I'll demonstrate both ways below:

Subview way:

struct HighlightedRectangle: View {
    let color: Color
    let highlightWidth, highlightHeight, highlightCorners: Float

    // body only called if any of the lets are different from last time this View was init by the parent view's body.
    var body: some View {
        Rectangle()
            .foregroundColor(color)
            .frame(width: highlightWidth, height: highlightHeight)
            .cornerRadius(highlightCorners)
    }
}

Then use it in the parent view as follows

let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
    HighlightedRectangle(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}

View modifier way:

struct Highlighted: ViewModifier {
    let color: Color
    let highlightWidth, highlightHeight, highlightCorners: Float

    // body only called if any of the lets are different from last time this ViewModifier was init by the parent view's body.
    func body(content: Content) -> some View {
        content
            .foregroundColor(color)
            .frame(width: highlightWidth, height: highlightHeight)
            .cornerRadius(highlightCorners)
    }
}

// this just makes the syntax for using the modifier simpler.
extension View {
    func highlighted(color: Color, highlightWidth: Float, highlightHeight: Float, highlightCorners: Float) -> some View {
        modifier(Highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners))
    }
}

Then use it in the parent view as follows

let colors = [.yellow, .cyan, .red, .white]
...
ForEach(colors, id: \.self) { color in {
    Rectangle()
        .highlighted(color: color, highlightWidth: highlightWidth, highlightHeight: highlightHeight, highlightCorners: highlightCorners)
}
  • Related