12

I know how a custom ButtonStyle works. But I do want to have a fully reusable custom button.

Aka a button with Text and and Icon that has some styles applied.

I know how to achieve this using a ButtonStyle with a text property, but I think this is completely a misuse of button styles.

But I do not want to copy a Button with a large HStack as content all over my app.

What do you recommend in such use cases? It seems that no one has subclassed a Button in SwiftUI on the internet.

5 Answers 5

36

It seems that no one has subclassed a Button in SwiftUI on the internet.

True. But that's because it's not possible - Button is a struct, so it can't be subclassed. Instead, what you can do is make a custom View.

struct CustomButton: View {
    var text: String
    var icon: Image
    var clicked: (() -> Void) /// use closure for callback
    
    var body: some View {
        Button(action: clicked) { /// call the closure here
            HStack {
                Text(text) /// your text
                
                icon /// your icon image
            }
            .foregroundColor(Color.green)
            .padding()
            .background(Color(.secondarySystemBackground))
            .cornerRadius(16)
        }
    }
}

struct ContentView: View {
    var body: some View {
        CustomButton(
            text: "Custom Button",
            icon: Image(systemName: "plus")
        ) {
            print("Clicked!")
        }
    }
}

Result:

"Custom Button +" button

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you! That's a great example of 'compose instead of subclass' exactly at a place where I furiously tried to subclass :).
4

You can actually create Styles in Swift UI, for example ButtonStyle

Here is the sample code, you could copy-paste and try it

import SwiftUI

struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        return configuration.label
            .padding()
            .background(Color.green)
            .foregroundColor(Color.white)
            .opacity(configuration.isPressed ? 0.7 : 1)
            .scaleEffect(configuration.isPressed ? 0.8 : 1)
            .animation(.easeInOut(duration: 0.2))
    }
}


//  USAGE:
Button(action: { }) {
  Text("This is a custom button")
}
.buttonStyle(CustomButtonStyle())

1 Comment

Yes, but there is no Icon / fully customized button. I know that ButtonStyles exist, but not to have a custom content of a Button reusable. :)
4
struct DarkButton: View {

   var buttonText: String = ""

   var body: some View {
       ZStack {
           Text(buttonText)
           .customDarkButtonStyle()
       }
   }
}

// MARK: - View Modifier to style our button -
/// If you need more customization add @State var

struct DarkButtonStyle: ViewModifier {

   /// Background button color
   var backgroundColor = Color.red

   /// Foreground button color
   var foregroundColor = Color.white

   /// Button width
   var width = 148

   /// Button height
   var height = 18

   /// Button corner radius
   var cornerRadius = 8

   func body(content: Content) -> some View {

       content
           .foregroundColor(foregroundColor)
           .font(/* Your font here */)
           .frame(width: width, height: height)
           .background(backgroundColor)
           .cornerRadius(cornerRadius)
   }
}

// MARK: - Cleaner way to call our custom ViewModifier -

extension View {
    func customDarkButtonStyle() -> some View {
        ModifiedContent(
            content: self,
            modifier: DarkButtonStyle()
        )
    }
}

Comments

1

You can of course fully customize the button with ButtonStyle. The makeBody just has to return a view, and configuration.label is just a label. No magic involved. Just wrap the label anyway you see fit. You can e.g. do like this to add an icon to the button:

struct CustomButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        return
            HStack {
                Image("imageName")
                configuration.label
            }
            .padding()
            .background(Color.green)
            .foregroundColor(Color.white)
            .opacity(configuration.isPressed ? 0.7 : 1)
            .scaleEffect(configuration.isPressed ? 0.8 : 1)
            .animation(.easeInOut(duration: 0.2))
    }
}

Comments

0

You can use a ViewModifier.

The code below is a ViewModifier for a custom back button.

struct OverlayBackButton: ViewModifier {
    @Environment(\.presentationMode) var presentation
    
    func body(content: Content) -> some View {
        content
            .overlay(
                Button(action: {
                    presentation.wrappedValue.dismiss()
                }) {
                Image(systemName: "chevron.backward") // set image here
                    .aspectRatio(contentMode: .fit)
                    .foregroundColor(.red)
                Text("Back")
                    .foregroundColor(.red)
                }
                .padding()
                ,alignment: .topLeading
            )
    }
}

extension View {
    func overlayBackButton() -> some View {
        self.modifier(OverlayBackButton())
    }
}

Then you can use the button like this

ZStack{

}
.overlayBackButton()

1 Comment

Thanks Taeeun, but this does also not allow to use the Text I will enter in the caller to be visible, wouldn't it?

Your Answer

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.