31

Seems like it should be simple.

Button(action: {
}){
    ZStack {
        Circle()
            .frame(width: 100, height: 100)
            .foregroundColor(.blue)
        Text("Press me")
    }
}

This gives me:

Preview

I can only click on the rectangle part. Also bonus points if you can point out why the circle is cut off

Turns out this is an issue with macOs - Issue with Buttons in SwiftUI on MacOS

2
  • 4
    You just need to use PlainButtonStyle as in this post and customise button as you want. Commented Jan 8, 2020 at 5:32
  • @Asperi, it is true partially only, try to check resulting size ...there is still trouble with vertical dimension. Commented Jan 8, 2020 at 19:11

6 Answers 6

55

Try this one:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: {
            print("Round Action")
            }) {
            Text("Press")
                .frame(width: 100, height: 100)
                .foregroundColor(Color.black)
                .background(Color.red)
                .clipShape(Circle())
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Output wil be:

enter image description here

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

Comments

4

Why the circle is cut off ??

When you apply an overlay to a view, the original view continues to provide the layout characteristics for the resulting view.

Unfortunately, it is true even for .background() modifier!!

You need to change the frame of the button, especially on mac, where by default configuration the button's background is visible.

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button(action: {
            print("tap")
        }) {
            Text("Button").font(.largeTitle)
        }.buttonStyle(BlueCircleButtonStyle())
        // to see resulting layout bounds
        .border(Color.red)
    }
}

struct BlueCircleButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label.padding().modifier(MakeSquareBounds()).background(Circle().fill(Color.blue))

    }
}

struct MakeSquareBounds: ViewModifier {

    @State var size: CGFloat = 1000
    func body(content: Content) -> some View {
        let c = ZStack {
            content.alignmentGuide(HorizontalAlignment.center) { (vd) -> CGFloat in
                DispatchQueue.main.async {
                    self.size = max(vd.height, vd.width)
                }
                return vd[HorizontalAlignment.center]
            }
        }
        return c.frame(width: size, height: size)
    }
}

the result running on mac enter image description here

Tap on the blue to make an action ...

There is a way to style it different while pressed (check ButtonStyle properties)

1 Comment

@ShadyAmoeba I changed my answer.
2

To make SwiftUI's animated Circular Button in macOS, use the following code:

import SwiftUI

struct CircleButton: View {        
    @State private var tapped = Bool()
    @State private var counter: Int = 0
    
    var body: some View {
        VStack {
            ZStack {
                Circle()
                    .fill(.white)
                    .frame(width: 105, height: 105)
                    .shadow(color: .gray.opacity(0.5), radius: 10, x: 7, y: 7)
                Image(systemName: "swift")
                    .foregroundColor(.black)
                    .font(.system(size: 50, weight: .semibold))
            }
            .scaleEffect(tapped ? 0.95 : 1)
            .onTapGesture {
                tapped.toggle()
                counter += 1
                
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
                    tapped = false
                }
            }
            Text("Tapped \(counter) times")
                .foregroundColor(.black)
                .font(.title2)
                .offset(y: 30)
        }
    }
}

struct ContentView: View {
    var body: some View {
        ZStack {
            Color(red: 0.72, green: 0.77, blue: 0.85)
            CircleButton()
        }
        .ignoresSafeArea()
    }
}

enter image description here

Comments

2

I didn't want to hard code a frame size, instead I wanted the circle to expand to the size of the text and came up with this:

Text("Click Me! Click Me! Click Me!")
    .allowsHitTesting(false) // allow taps exactly on the text to make it through to the button
    .padding()
    .background {
        Button {
            // button action
            print("Tapped")
        } label: {
                Circle()
                    .foregroundColor(.green)
                    .scaledToFill()
        }
        .contentShape(Circle()) // don't allow taps outside the circle
    }

Screenshot of a circular button that has scaled to fit text

Comments

1

(Solution extracted from the question)


You can use PlainButtonStyle:

    var body: some View {
        VStack{
            Button(action: {
                print("Pressed!")
            }){
               Text("Press me")
               .frame(width: 100, height: 100)
               .foregroundColor(Color.black)
               .background(Color.red)
               .clipShape(Circle())
            }.buttonStyle(PlainButtonStyle())
        }.frame(width: 300, height: 500)
        
    }
}

or use a custom style:

struct BlueButtonStyle: ButtonStyle {
    func makeBody(configuration: Self.Configuration) -> some View {
        configuration.label
            .frame(width: 100, height: 100)
            .foregroundColor(Color.black)
            .background(Color.red)
            .clipShape(Circle())
    }
}

struct ContentView: View {
    var body: some View {
        VStack{
            Button(action: {
                print("Pressed!")
            }){
               Text("Press me")
               
            }.buttonStyle(BlueButtonStyle())
        }.frame(width: 300, height: 500)
        
    }
}

Comments

0

My circular button looks like this:

Button {
          viewModel.callEmergency()
       } label: {
                  ZStack {
                    Circle()
                      .background(Color.red)
                    Image(systemName: "light.beacon.max.fill")
                      .foregroundColor(.white)
                    }
                }
                .frame(width: 100, height: 100)
                .clipShape(Circle())
                .foregroundColor(.red)
                .opacity(0.86)

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.