Home > Software engineering >  In SwiftUI how to define a function pointer to the HStack or VStack constructor
In SwiftUI how to define a function pointer to the HStack or VStack constructor

Time:02-10

I have this view and would like to simplify it:

struct SUIBorderedIconTextButton: View {
  
  struct IconInfo {
    enum Position {
      case left
      case right
      case top
      case bottom
      
      var isHorizontal: Bool {
        return self == .left || self == .right
      }
      
      var isBeforeText: Bool {
        return self == .left || self == .top
      }
    }
                    
    let icon: Image
    let position: Position
  }
  
  let iconInfo: IconInfo?
  let title: String
  let fontSize: CGFloat
  let backgroundColor: Color
  let enabled: Bool
  let action: () -> Void
  
  @ViewBuilder
  private var content: some View {
    if let iconInfo = iconInfo {
      switch iconInfo.position {
      case .left:
        HStack {
          iconInfo.icon
          Text(title).fontSize(fontSize)
        }
      case .right:
        HStack {
          Text(title).fontSize(fontSize)
          iconInfo.icon
        }
      case .top:
        VStack {
          iconInfo.icon
          Text(title).fontSize(fontSize)
        }
      case .bottom:
        VStack {
          Text(title).fontSize(fontSize)
          iconInfo.icon
        }
      }
    } else {
      Text(title).fontSize(fontSize)
    }
  }
  
  var body: some View {
    Button(action: action) {
      content
        .padding()
        .foregroundColor(color_button_text)
        .background {
          RoundedRectangle(cornerRadius: text_button_border_radius)
            .foregroundColor(enabled ? backgroundColor : color_disabled_background)
        }
    }
    .disabled(!enabled)
  }
}

This view simply displays a text button, with optional icon (either on left/right/top/bottom of the text)

if position is left or right, we want to use HStack. Otherwise, use VStack.

if position is top or left, we want to put icon before text. Otherwise, put icon after text.

Note that there are quite a lot of boilerplate in the content builder.

I created this helper function for the .top or .left cases:


  @ViewBuilder
  private func group(iconInfo: IconInfo) -> some View {
    if iconInfo.position.isBeforeText {
      iconInfo.icon
      Text(title).fontSize(fontSize)
    } else {
      Text(title).fontSize(fontSize)
      iconInfo.icon
    }
  }

But I am not sure how to handle choosing the constructor. I may need a pointer to the init function, but I have tried

let constructor = position.isHorizontal ? HStack.init : VStack.init
constructor {
  ... 
}

This obviously does not compile. I am not sure how to proceed. If you have other ideas to simplify this logic, feel free to try it out too.

CodePudding user response:

Ternary operator actually is not applicable to SwiftUI types as they all value-types with generics so result in constructed different type entities and cannot be assigned to one variable.

Instead possible approach for your case is something like

@ViewBuilder var content: some View {
    Text("Text")
    Image(systemName: "checkmark")
}

var body: some View {
    if isHorizontal {
        HStack{ content }
    } else {
        VStack{ content }
    }
}
  • Related