I want to add a button with an image (on the left), as follows:
Probably, it seems to be similar to setImage(_:for:) when it comes to UIButton.
What I have done so far is:
Button(action: {}) {
Text("Add To Cart")
.foregroundColor(.black)
.padding(.all, 10.0)
}
.background(Color.gray)
.cornerRadius(5.0)
And the result is pretty close to the required one:
However, I couldn't find any property/method in SwiftUI Button related to setting an image.
How to resolve it?
You could implement it as:
Button(action: {}) {
HStack(alignment: .center, spacing: 5.0) {
Image("cart")
.padding(.leading, 10.0)
Text("Add to Cart")
.foregroundColor(.black)
.padding(.all, 10.0)
}
}
.background(Color.gray)
.cornerRadius(5.0)
Passing HStack instead of Text would do the trick.
To clarify, adding an image next to the button label is not directly related to the button anymore! When creating a button using init(action:label:) initializer, we are passing () -> Label as the second parameter; If you trace the type of Label, you'll see that it is basically a View, so you are not forced to return a Text type for it (as mentioned in the question's code snippet).
Obviously, you could add the image to the right of the label by adding Text before the Image. Also, if you are aiming to add the text on top of the image (or vice-versa), you could simply replace the HStack with a VStack.
Furthermore, more reusable code
You could make your code to be more reusable by creating a custom body View for the button. Also, you could create a custom ViewModifier:
import SwiftUI
// Custom View
struct CustomButtonBody: View {
private var iconName: String
private var title: String
init(iconName: String = "cart", title: String) {
self.iconName = iconName
self.title = title
}
var body: some View {
HStack(alignment: .center, spacing: 5.0) {
Image(iconName)
.padding(.leading, 10.0)
Text(title)
.foregroundColor(.black)
.padding(.all, 10.0)
}
}
}
// Custom Modifier
struct CustomButtonModifier: ViewModifier {
func body(content: Content) -> some View {
return content
.background(Color.gray)
.cornerRadius(5.0)
}
}
thus:
Button(action: {}) {
CustomButtonBody(title: "Add to Cart")
}
.modifier(CustomButtonModifier())
Why don't you just
Button(action: {}) {
Label("Add to cart", systemImage: "cart")
}
Related
I am trying to create a layout in which there is a list of elements, and a button to add more elements in front of it. I know I could put it in the navigation bar, but that's not the graphic look I'd like to achieve. However, if I put those two elements inside a ZStack, the ForEach becomes overlapped, even though it's within a VStack. How can I solve this?
import SwiftUI
struct ContentView: View {
let arrayTest = ["Element 1", "Element 2", "Element 3"]
var body: some View {
NavigationView {
ZStack {
VStack {
ForEach(arrayTest, id: \.self) { strings in
Text(strings)
}
}
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
//AddView
}) {
Image(systemName: "plus")
.background(Circle().foregroundColor(.yellow))
}.padding(.trailing, 20)
.padding(.bottom, 20)
}
}
}
}
}
}
Edit: To be more precise, I would like the button to be over the ForEach, because if I used a VStack and the list of elements was very long, the user would have to scroll all the way to the bottom to find the button. With a ZStack, it would always be visible no matter the point of the list the user is at.
Here is one way of using overlay, see example in code:
struct ContentView: View {
#State private var arrayTest: [String] = [String]()
var body: some View {
NavigationView {
Form { ForEach(arrayTest, id: \.self) { strings in Text(strings) } }
.navigationTitle("Add Elements")
}
.overlay(
Button(action: { addElement() })
{ Image(systemName: "plus").font(Font.largeTitle).background(Circle().foregroundColor(.yellow)) }.padding()
, alignment: .bottomTrailing)
.onAppear() { for _ in 0...12 { addElement() } }
}
func addElement() { arrayTest.append("Element " + "\(arrayTest.count + 1)") }
}
Here use the same button view in navigationbaritem and ContentView.
have idea to solve this problem?
https://imgur.com/a/jCxfVSa
struct myButton: View {
var body: some View {
Button(action: { print("qwe\(Int.random(in: 1...100))") }) {
Image( "play")
.resizable()
.scaledToFit()
.background(Color.red)
}
.frame(width: 40, height: 40, alignment: /*#START_MENU_TOKEN#*/.center/*#END_MENU_TOKEN#*/)
}
}
struct ContentView: View {
var body: some View {
NavigationView {
myButton()
.navigationBarItems(leading:
myButton()
)
.navigationTitle("Title")
.navigationBarTitleDisplayMode(.inline)
}
}
enter image description here
Natively in SwiftUI you can't use shaping on toolbar icons
(you can, but sometimes navigation bar icons loses clipshapes (after navigationLink, .sheet, .alert....)
(on SwiftUI 2 prefer to use .toolbar{}, no .navigationBarItems() )
But, you can use SwiftUIX library on GitHub
I'm seeing a strange issue that I was able to reproduce with a small sample. If you have a detail view that has navigationBarItems set, and that detail is the second view pushed on a navigation stack, the items do not show up when you get to the detail page. Here is the sample:
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: MiddleTestView()) {
Text("Push View")
}
}
}
}
}
struct MiddleTestView: View {
var body: some View {
VStack {
NavigationLink(destination: TestView()) {
Text("Push Another View")
}
}
}
}
struct TestView: View {
var body: some View {
VStack {
Text("Testing 1, 2, 3")
}
.navigationBarItems(leading: Button("Test") { print("pressed") })
}
}
If anything causes the TestView to re-render, then the button will show, for instance if the TestView does this:
struct TestView: View {
#State var hasChanges = false
var body: some View {
VStack {
Text("Testing 1, 2, 3")
Button("Toggle") { hasChanges = !hasChanges }
}
.navigationBarItems(leading: Button(hasChanges ? "Test1" : "Test2") { print("pressed") })
}
}
Then pressing the "Toggle" button once will cause the navigationBarItems to appear, and they will stay there until the view is dismissed. Additionally, if the TestView is shown first, instead of the MiddleTestView, then there is no problem with the navigationBarItems. I cannot see any reason for this behavior, it seems like a pretty glaring bug that makes working with navigation stacks in SwiftUI fundamentally broken, unless I'm missing something. Does anyone have any insight into what is going on here or how to get around it?
I'm trying to figure out layout mechanisms in SwiftUI, and while its fairly straightforward, I don't know what I'm missing here.
According to me, the following code should fill the device's entire screen with the colour green, but for some reason, it leaves a small gap at the bottom of the screen. This is not the safe area as the gap is left even in older devices such as the iPhone SE.
I know I can achieve the same result just by changing the screen colour to green; as I said I'm trying to figure out layout in SwiftUI.
I'd really appreciate any help.
struct ContentView: View {
var body: some View {
VStack {
Green()
.edgesIgnoringSafeArea(.all)
}
}
}
struct Green: View {
let hh = UIScreen.main.bounds.height
let ww = UIScreen.main.bounds.width
var body: some View {
ZStack {
Rectangle()
.fill(Color.green)
.frame(width: ww, height: hh, alignment: .center)
}
}
}
This is due to used UIScreen restriction, instead it needs to use
struct Green: View {
var body: some View {
Rectangle()
.fill(Color.green)
}
}
and area filled completely, `cause detected dynamically. Tested with Xcode 11.4.
Simply use Rectangle() inside your ZStack. Here is your ContentView:
struct ContentView: View {
var body: some View {
ZStack {
// This is your green background
Rectangle()
.fill(Color.green)
.edgesIgnoringSafeArea(.all)
// Use other UI stuffs here
VStack {
Text("Use other UI components here")
}
}
}
}
Result:
You can achieve this using ZStack. add Color direct as a component
struct ContentView: View {
var body: some View {
ZStack {
Color.green
.edgesIgnoringSafeArea(.all)
Text("Use other UI components here")
}
}
}
How do I toggle the presence of a button to be hidden or not?
We have the non-conditional .hidden() property; but I need the conditional version.
Note: we do have the .disabled(bool) property available, but not the .hidden(bool).
struct ContentView: View {
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foreggroundColor(Color.white)
.cornerRadius(10)
.hidden() // ...I want this to be toggled.
}
}
}
}
I hope hidden modifier gets argument later, but since then, Set the alpha instead:
#State var shouldHide = false
var body: some View {
Button("Button") { self.shouldHide = true }
.opacity(shouldHide ? 0 : 1)
}
For me it worked perfectly to set the frame's height to zero when you do not want to see it. When you want to have the calculated size, just set it to nil:
SomeView
.frame(height: isVisible ? nil : 0)
If you want to disable it in addition to hiding it, you could set .disabled with the toggled boolean.
SomeView
.frame(height: isVisible ? nil : 0)
.disabled(!isVisible)
You can utilize SwiftUI's new two-way bindings and add an if-statement as:
struct ContentView: View {
#State var shouldHide = false
var body: some View {
ZStack {
Color("SkyBlue")
VStack {
if !self.$shouldHide.wrappedValue {
Button("Detect") {
self.imageDetectionVM.detect(self.selectedImage)
}
.padding()
.background(Color.orange)
.foregroundColor(Color.white)
.cornerRadius(10)
}
}
}
}
}
The benefit of doing this over setting the opacity to 0 is that it will remove the weird spacing/padding from your UI caused from the button still being in the view, just not visible (if the button is between other view components, that is).
all the answers here works specifically for a button to be hidden conditionally.
What i think might help is making a modifier itself conditionally e.g:
.hidden for button/view, or maybe .italic for text, etc..
Using extensions.
For text to be conditionally italic it is easy since .italic modifier returns Text:
extension Text {
func italicConditionally(isItalic: Bool) -> Text {
isItalic ? self.italic() : self
}
}
then applying conditional italic like this:
#State private var toggle = false
Text("My Text")
.italicConditionally(isItalic: toggle)
However for Button it is tricky, since the .hidden modifier returns "some view":
extension View {
func hiddenConditionally(isHidden: Bool) -> some View {
isHidden ? AnyView(self.hidden()) : AnyView(self)
}
}
then applying conditional hidden like this:
#State private var toggle = false
Button("myButton", action: myAction)
.hiddenConditionally(isHidden: toggle)
You can easily hide a view in SwiftUI using a conditional statement.
struct TestView: View{
#State private var isVisible = false
var body: some View{
if !isVisible {
HStack{
Button(action: {
isVisible.toggle()
// after click you'r view will be hidden
}){
Text("any view")
}
}
}
}
}
It isn't always going to be a pretty solution, but in some cases, adding it conditionally may also work:
if shouldShowMyButton {
Button(action: {
self.imageDetectionVM.detect(self.selectedImage)
}) {
Text("Button")
}
}
There will be an issue of the empty space in the case when it isn't being shown, which may be more or less of an issue depending on the specific layout. That might be addressed by adding an else statement that alternatively adds an equivalently sized blank space.
#State private var isHidden = true
VStack / HStack
if isHidden {
Button {
if !loadVideo(),
let urlStr = drill?.videoURL as? String,
let url = URL(string: urlStr) {
player = VideoPlayerView(player: AVPlayer(), videoUrl: url)
playVideo.toggle()
}
} label: {
Image(playVideo ? "ic_close_blue" : "ic_video_attached")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 50)
}
.buttonStyle(BorderlessButtonStyle())
}
.onAppear {
if shouldShowButton {
isHidden = false
} else {
isVideoButtonHidden = true
}
}