Button border with corner radius in Swift UI - ios

I'm trying to set a rounded border to a button but the border of the button is not correct.
Code:
Button(action: {
print("sign up bin tapped")
}) {
Text("SIGN UP")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
}
.border(Color.white, width: 2)
.cornerRadius(25)
Output:
As you can see the border at corner are cut-off.
Any suggestion what am I doing wrong?

Instead of setting the cornerRadius to the Button use an overlay for the inside View:
Edit: If you have a background for the button you also need to apply the cornerRadius to the background.
Button(action: {
print("sign up bin tapped")
}) {
Text("SIGN UP")
.frame(minWidth: 0, maxWidth: .infinity)
.font(.system(size: 18))
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 25)
.stroke(Color.white, lineWidth: 2)
)
}
.background(Color.yellow) // If you have this
.cornerRadius(25) // You also need the cornerRadius here

Updated for Swift 5 & iOS 13.4+ with Press States!
None of the examples worked for buttons with both dark and white background colors as well as none of them had press state updates, so I built this LargeButton view that you can see below. Hope this helps, should be pretty simple to use!
Example Photos
Example Use
// White button with green border.
LargeButton(title: "Invite a Friend",
backgroundColor: Color.white,
foregroundColor: Color.green) {
print("Hello World")
}
// Yellow button without a border
LargeButton(title: "Invite a Friend",
backgroundColor: Color.yellow) {
print("Hello World")
}
Code
struct LargeButtonStyle: ButtonStyle {
let backgroundColor: Color
let foregroundColor: Color
let isDisabled: Bool
func makeBody(configuration: Self.Configuration) -> some View {
let currentForegroundColor = isDisabled || configuration.isPressed ? foregroundColor.opacity(0.3) : foregroundColor
return configuration.label
.padding()
.foregroundColor(currentForegroundColor)
.background(isDisabled || configuration.isPressed ? backgroundColor.opacity(0.3) : backgroundColor)
// This is the key part, we are using both an overlay as well as cornerRadius
.cornerRadius(6)
.overlay(
RoundedRectangle(cornerRadius: 6)
.stroke(currentForegroundColor, lineWidth: 1)
)
.padding([.top, .bottom], 10)
.font(Font.system(size: 19, weight: .semibold))
}
}
struct LargeButton: View {
private static let buttonHorizontalMargins: CGFloat = 20
var backgroundColor: Color
var foregroundColor: Color
private let title: String
private let action: () -> Void
// It would be nice to make this into a binding.
private let disabled: Bool
init(title: String,
disabled: Bool = false,
backgroundColor: Color = Color.green,
foregroundColor: Color = Color.white,
action: #escaping () -> Void) {
self.backgroundColor = backgroundColor
self.foregroundColor = foregroundColor
self.title = title
self.action = action
self.disabled = disabled
}
var body: some View {
HStack {
Spacer(minLength: LargeButton.buttonHorizontalMargins)
Button(action:self.action) {
Text(self.title)
.frame(maxWidth:.infinity)
}
.buttonStyle(LargeButtonStyle(backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
isDisabled: disabled))
.disabled(self.disabled)
Spacer(minLength: LargeButton.buttonHorizontalMargins)
}
.frame(maxWidth:.infinity)
}
}

Official .bordered modifier support in iOS 15+
Buttons now have baked in border styling support using the .buttonStyle(.bordered) modifier. I would suggest using the corner radius Apple provides for these buttons for the best platform-specific styling. We can change the color to be consistent with the system styles for buttons and tint the background as well as text using the .tint modifier:
Button("Add") { ... }
.buttonStyle(.bordered)
.tint(.green)
You can make the tint color more prominent (bolder) using .borderedProminent and control the size using .controlSize:
Button("food") { ... }
.tint(.red)
.controlSize(.small) // .large, .medium or .small
.buttonStyle(.borderedProminent)
You can also use this modifier on parent Views of Buttons and toggle lighter color schemes using .accentColor in child Buttons:
ScrollView {
LazyVStack {
Button("Test Button 1") { ... }
.buttonStyle(.borderedProminent)
.keyboardShortcut(.defaultAction) // Tapping `Return` key actions this button
Button("Test Button 2") { ... }
.tint(.accentColor)
}
}
.buttonStyle(.bordered)
.controlSize(.large)
Advice
Apple for some reason doesn't like single-line bordered buttons which is why the .border() modifier was deprecated in Xcode 12. With this change, I suggest developers avoid creating single-line bordered buttons because they now are not preferred in Apple's Human Interface Guidelines. Using prominent buttons everywhere also violates HIG.
Extra NOTE: Apple's .bordered style provides the standard platform style across device types. In addition, the Button responds to Dark Mode dynamically and scales its size with Dynamic Type (native accessibility support).

Swift 5 & iOS 14 – Borders also react when pressed
struct PrimaryButtonStyle: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(5)
.foregroundColor(configuration.isPressed ? Color.red.opacity(0.5) : .red)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(configuration.isPressed ? Color.red.opacity(0.5) : .red, lineWidth: 1.5)
)
}
}
How to use
Button("Hide") {
print("tapped")
}.buttonStyle(PrimaryButtonStyle())
borders also react when pressed

Xcode 11.4.1
Button(action: self.action) {
Text("Button Name")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(.white)
.padding(10)
.background(Color.darkGray)
.cornerRadius(10)
}
.buttonStyle(PlainButtonStyle())
There isn't a need to add an overlay. You can substitute padding modifier with frame modifier. The action is a non return method outside of the body variable.
Right specifically for #MinonWeerasinghe:
Button(action: self.action) {
Text("Button Name")
.font(.system(size: 15))
.fontWeight(.bold)
.foregroundColor(.black)
.padding(10)
.background(RoundedRectangle(cornerRadius: 10).stroke().foregroundColor(Color.red))
.cornerRadius(10)
}
.buttonStyle(PlainButtonStyle())

Just add the cornerRadius argument:
.border(Color.white, width: 2, cornerRadius: 25)
using this simple extension:
extension View {
func border(_ color: Color, width: CGFloat, cornerRadius: CGFloat) -> some View {
overlay(RoundedRectangle(cornerRadius: cornerRadius).stroke(color, lineWidth: width))
}
}

You can try this:
var body: some View {
ZStack {
Color.green
.edgesIgnoringSafeArea(.all)
HStack {
Button(action: {
print("sign up bin tapped")
}){
HStack {
Text("SIGN UP")
.font(.system(size: 18))
}
.frame(minWidth: 0, maxWidth: 300)
.padding()
.foregroundColor(.white)
.overlay(
RoundedRectangle(cornerRadius: 40)
.stroke(Color.white, lineWidth: 2)
)
}
}
}
}
I also did not set the maxWidth to .infinity because it means the button will fill the width of your container view.
The result will be :
Hope it helps :)

Swift version 5.6
You can use Button properties for example
Button(action: {
//define action
}) {
Image(systemName: "arrow.triangle.2.circlepath.circle.fill")
.imageScale(.large)
Text("Restart")
.font(.system(.title2))
}
.buttonStyle(.borderedProminent)
.buttonBorderShape(.capsule)
.controlSize(.large)
.buttonBorderShape(.roundedRectangle) //change bordershape see below
.buttonBorderShape(.roundedRectangle(radius: 4)) // see below
similarly you can change the buttonSytle and controlSize

This worked for me
Button(action: {
print("Exit the onboarding")
}) {
HStack (spacing: 8) {
Text("NEXT")
.foregroundColor(Color("ColorAccentOppBlack"))
}
.padding(.horizontal, 16)
.padding(.vertical, 10)
.foregroundColor(Color("ColorYellowButton"))
.background(
Capsule().strokeBorder(Color("ColorYellowButton"), lineWidth: 1.25)
)
}
.accentColor(Color("ColorYellowButton"))

You should use Capsule. This is built-in into SwiftUI. It takes care of rounded corners. Full implementation is here https://redflowerinc.com/how-to-implement-rounded-corners-for-buttons-in-swiftui/
public struct ButtonStyling : ButtonStyle {
public var type: ButtonType
public init(type: ButtonType = .light) {
self.type = type
}
public func makeBody(configuration: Configuration) -> some View {
configuration.label.foregroundColor(Color.white)
.padding(EdgeInsets(top: 12,
leading: 12,
bottom: 12,
trailing: 12))
.background(AnyView(Capsule().fill(Color.purple)))
.overlay(RoundedRectangle(cornerRadius: 0).stroke(Color.gray, lineWidth: 0))
}
}

🔴 To create a border with rounded corners, you can draw a rounded rectangle and overlay on the button like this:
Button(action: {
print("Hello button tapped!")
}) {
Text("Hello World")
.fontWeight(.bold)
.font(.title)
.foregroundColor(.purple)
.padding()
.overlay(
RoundedRectangle(cornerRadius: 20)
.stroke(Color.purple, lineWidth: 5)
)
}

Wonder how to add button border with color gradient and corner radius
Here's how..
Button(action: {self.isCreateAccountTapped = true},label: {Text("Create an Account")
.foregroundColor(Color("TextThemeColor36"))}
)
.frame(height: 44)
.frame(width: 166)
.background(Color.clear)
.cornerRadius(8)
.overlay(RoundedRectangle(cornerRadius: 10)
.stroke(LinearGradient(gradient: Gradient(colors: [Color("BtnGradientClr1"),Color("BtnGradientClr2"),Color("BtnGradientClr3")]), startPoint: .leading, endPoint: .trailing)))

Related

Textfield default color

I have code like this:
VStack {
ForEach(Array($ingredientNames.enumerated()), id:\.offset) { (idx, $str) in
if idx != 0{
TextField("Player \(idx + 1)", text: $str)
.foregroundColor(Color.white)
.fontWeight(.bold)
.shadow(color: .white, radius: 20)
.background(Color("AccentColor"))
.multilineTextAlignment(.center)
.font(Font.system(size: 26))
}
else {
TextField("Player", text: $str)
.foregroundColor(Color.white)
.fontWeight(.bold)
.shadow(color: .white, radius: 20)
.background(Color("AccentColor"))
.multilineTextAlignment(.center)
.font(Font.system(size: 26))
}
}
}
But actually the color of "player" is gray (default). How can I make it for example white? Adding a .foregroundColor doesn't work as you can see - now it's working when user type something - then it's turning white. But how can I make it white as a default with no need to input something into this to work?
Add this extension to your project:
extension View {
func placeholder<Content: View>(
when present: Bool,
alignment: Alignment = .leading,
#ViewBuilder content: () -> Content) -> some View {
ZStack(alignment: alignment) {
content().opacity(present ? 1 : 0)
self
}
}
}
Then in your code use this:
TextField("", text: $text)
.placeholder(when: text.isEmpty) {
Text("Player").foregroundColor(.white)
}
.foregroundColor(Color.white)
.fontWeight(.bold)
.shadow(color: .white, radius: 20)
.background(Color("AccentColor"))
.multilineTextAlignment(.center)
.font(Font.system(size: 26))

SwiftUI: add Text below a Button

So I'm building my first App and trying to get some Text below the Button, but it should still be a part of the button. So thats what i got:
Button{
print("tapped")
} label: {
Image("Wand")
.resizable()
.frame(width: 100, height: 100)
}
.font(.title3)
.background(Color.gray)
.foregroundColor(.black)
.cornerRadius(20)
It looks like this:
But I want is this:
I've tried putting a VStack into the Label, but the Text stays in the gray button
I think it may help you
public struct VerticalLabelStyle: LabelStyle {
public let spacing: CGFloat
public init(spacing: CGFloat = 8) {
self.spacing = spacing
}
public func makeBody(configuration: Configuration) -> some View {
VStack(alignment: .center, spacing: spacing) {
configuration.icon
configuration.title
}
}
}
public struct ContentView: View {
#State private var text: String = ""
public var body: some View {
Label {
Text(text)
} icon: {
Image(systemName: "gamecontroller.fill")
.resizable()
.frame(width: 44, height: 44, alignment: .center)
}
.frame(width: 100, height: 100)
.labelStyle(VerticalLabelStyle())
.onTapGesture(perform: onTap)
}
func onTap() {
text = "tapped"
}
}
If you have some questions please ask
Your idea with a VStack is the correct way. You just have to keep in mind where to place your modifiers. In your case you placed them on the button level. Meaning they are applied to the entire label, which is the reason why your Text is inside the gray background. Just update also the place of your modifiers inside your VStack.
Here is are short example:
Button {
// Your button action
} label: {
VStack {
RoundedRectangle(cornerRadius: 25)
.frame(width: 100, height: 100)
.padding()
.background(Color.black) // new position of background modifier that is only applied on your Image (here a rectangle)
Text("Foo")
.font(.title3)
.foregroundColor(.black)
// These modifiers could stay on button level, but I moved them here to explicitly show where they applied
}
}
Keep in mind you can put any view inside the label of your button and that in this case the Text is part of the button and will trigger the action if a user taps on it. If you only want that the Image is the tap action wrap your button inside a VStack containing only the Image and a Text as the second part of the Stack like this:
VStack {
Button {
// Your button action
} label: {
VStack {
RoundedRectangle(cornerRadius: 25)
.frame(width: 100, height: 100)
.padding()
.background(Color.black)
}
}
Text("Foo")
.font(.title3)
.foregroundColor(.black)
}

Change buttonStyle Modifier based on light or dark mode in SwiftUI

I want to set Custom buttonStyle modifier for button for light and dark mode.
How to change buttonStyle Modifier based on light or dark mode? I want to set Custom modifier for my button for light and dark mode.
here is my button code,
Button(action: {
print("button tapped")
}, label: {
LinearGradient(gradient: Gradient(colors: [.darkBlueColor, .lightBlueColor]), startPoint: .top, endPoint: .bottom)
.mask(Image(systemName: "ellipsis")
.resizable()
.aspectRatio(contentMode: .fit)
).frame(width: iPhoneSE ? 26 : 25, height: iPhoneSE ? 26 : 25, alignment: .center)
})
.buttonStyle(lightButtonStyle())
struct lightButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(10)
.background(
Group {
if configuration.isPressed {
Circle()
.fill(Color.offWhite)
.overlay(
Circle()
.stroke(Color.lightGray2, lineWidth: 4)
.blur(radius: 1)
.offset(x: 2, y: 2)
.mask(Circle().fill(LinearGradient(Color.black, Color.clear)))
)
.overlay(
Circle()
.stroke(Color.white, lineWidth: 4)
.blur(radius: 1)
.offset(x: -2, y: -2)
.mask(Circle().fill(LinearGradient(Color.clear, Color.black)))
)
} else {
Circle()
.fill(Color.offWhite)
.shadow(color: Color.white.opacity(0.8), radius: 1, x: -2, y: -2)
.shadow(color: Color.lightPurple.opacity(0.6), radius: 1, x: 2, y: 2)
}
}
)
}
}
For Dark mode i've another buttonStyle with different color and shadows.
i know we can change other modifiers like this,
.fill(colorScheme == .dark ? Color.darkEnd : Color.white)
But some how i'm not able to change buttonStyle modifier.
Just put that condition inside button style modifier, like
// ... other your code
})
.buttonStyle(CustomButtonStyle(scheme: colorScheme)) // << here !!
and in custom style
struct CustomButtonStyle: ButtonStyle {
var scheme: ColorScheme // << here !!
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.padding(10)
Group {
if configuration.isPressed {
Circle() // example of internal dependency on scheme
.fill(self.scheme == .dark ? Color.offBlack : Color.offWhite)
// .. other code here
}
You could define a named color in Assets.xcassets with a variation for the dark mode:
This works out of the box even in a ButtonStyle:
Color("ButtonBorder")

SwiftUI: How can I change the color change that occurs when the user holds down something like a navigation link or a button?

I have the problem that when the user holds down the navigation link, the color of the space around the respective "cards" changes. Of course, this is not what I want. But as a total SwiftUI beginner, I do not know how to fix it. But I really want to fix this bug because it is not very innovative and irritates the user. So I would appreciate it if someone finds a solution that changes the color of the card instead of the space around it.
I suspect that my problem is because the system considers the space on the sides and the corresponding cards as one list cell. And of course, just like anywhere else in the system, these list cells are changing color when the user holds them down.
And my code:
import SwiftUI
struct ContentView: View {
var body: some View {
UITableView.appearance().separatorStyle = .none
UITableViewCell.appearance().backgroundColor = .none
return NavigationView {
List {
Cards()
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
Cards()
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
Cards()
.listRowInsets(EdgeInsets(top: 0, leading: 16, bottom: 16, trailing: 16))
} .listStyle(GroupedListStyle()).padding(.bottom, -32) // GroupedListStyle is needed for the background color
// Navigation bar
.navigationBarTitle(Text("Header"))
.navigationBarItems(
leading:
Button(action: { print("add") }) {
Image(systemName: "plus")
},
trailing:
Button(action: { print("edit") }) {
Text("Edit")
}.disabled(true)
)
}
}
}
// For the Cards on the main screen
struct Cards: View {
var body: some View {
VStack(spacing: 0) {
Image("swiftui")
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack(alignment: .leading, spacing: 0) {
Text("Title")
.font(.title)
.foregroundColor(.primary)
.lineLimit(3)
Text("Subtitle")
.font(.caption)
.foregroundColor(.secondary)
}
.layoutPriority(100)
Spacer()
NavigationLink(destination: EmptyView()) {
EmptyView()
}.opacity(0) // Hiding the default navigation bar chavron
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
.font(Font.body.weight(.semibold))
}
.padding(.all, 16)
.background(Color("CustomCardBackgroundColor")) // This is a custom color set
}
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1)
)
}
}
This is due to the design of List; when you click on a row, the whole row is highlighted. This is not a configurable setting, at least at this point.
One option would be to replace List { ... } with ScrollView { VStack { ... } }. This would also require you to move your NavigationLink to the top level of your Card view, set a PlainButtonStyle on the NavigationLink so it doesn't turn your image blue, and add some padding around the edges.
Note that you will have trouble setting a background color behind the cells. Here are a couple questions that try to address it, but I could not successfully combine any of those methods with your views. For now, you will probably just have to pick which you like better: custom background color, or tap coloration only being applied to the cards.
struct StackOverflowTests: View {
var body: some View {
return NavigationView {
// CHANGED THIS
ScrollView {
VStack {
Cards()
Cards()
Cards()
}.padding(.horizontal)
}
// Navigation bar
.navigationBarTitle(Text("Header"))
.navigationBarItems(
leading:
Button(action: { print("add") }) {
Image(systemName: "plus")
},
trailing:
Button(action: { print("edit") }) {
Text("Edit")
}.disabled(true)
)
}
}
}
and
// For the Cards on the main screen
struct Cards: View {
var body: some View {
// MOVED THIS
NavigationLink(destination: EmptyView()) {
VStack(spacing: 0) {
Image("swiftui")
.resizable()
.aspectRatio(contentMode: .fit)
HStack {
VStack(alignment: .leading, spacing: 0) {
Text("Title")
.font(.title)
.foregroundColor(.primary)
.lineLimit(3)
Text("Subtitle")
.font(.caption)
.foregroundColor(.secondary)
}
.layoutPriority(100)
Spacer()
Image(systemName: "chevron.right")
.foregroundColor(.secondary)
.font(Font.body.weight(.semibold))
}
.padding(.all, 16)
.background(Color("CustomCardBackgroundColor")) // This is a custom color set
}
.cornerRadius(10)
.overlay(
RoundedRectangle(cornerRadius: 10)
// ATTENTION NEEDED: You will probably need to make this border darker/wider
.stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1)
)
}
// ADDED THIS
.buttonStyle(PlainButtonStyle())
}
}
this happens because of your padding
.padding(.bottom, -32)
on the list.

How to extend the width of a button using SwiftUI

I cannot figure out how to change the width of buttons in SwiftUI.
I have already attempted:
using .frame(minWidth: 0, maxWidth: .infinity),
using Spacer() around the button and navigationlink,
using frame on the Text field and padding on the button, look through the documentation, as well as a few other things I found while just searching online. However nothing changes the buttons width whatsoever.
NavigationLink(destination: Home(), isActive: self.$isActive) { Text("") }
Button(action: { self.isActive = true }) { LoginBtn() }
struct LoginBtn: View {
var body: some View {
Text("Login")
.fontWeight(.bold)
.padding()
.foregroundColor(Color.white)
.background(Color.orange)
.cornerRadius(5.0)
}
}
Photo of current button
I would like to have the button to extend to be similar to the width of the TextFields used. Again, I know there have been answers posted but for some reason I cannot get mine to work. Thanks!
Declare your own button style:
struct WideOrangeButton: ButtonStyle {
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding()
.frame(minWidth: 0,
maxWidth: .infinity)
.foregroundColor(.white)
.padding()
.background( RoundedRectangle(cornerRadius: 5.0).fill(Color.orange)
)
}
}
and then use it like this:
Button(action: { self.isActive = true }) {
Text("Login")
.fontWeight(.bold)
} .buttonStyle(WideOrangeButton())
I like this approach since it lets me use the default button style, but still results in a wider button.
Button(action: {
// Whatever button action you want.
}, label: {
Text("Okay")
.frame(maxWidth: .infinity)
})
.buttonStyle(.automatic)

Resources