Avoid button styling its content in SwiftUI - ios

I'm creating an iOS app using Apple's SwiftUI framework. As I need to detect if the user taps on a specific area of the screen, I obviously use a button.
The problem is that the area contains an Image and a Text, and as the button automatically gives its content the blue color, the image is also colored, so instead of being an Image it's just a blue rounded rectangle.
It is said that an image is worth a thousand words, and as I'm not good at explaining, here you have a graphic demonstration of what happens:
Outside the button (without button styling)
Inside the button (with button styling)
This happens because the button is adding .foregroundColor(.blue) to the image.
How can I avoid/disable the button adding style to its components?
EDIT: This is my button code:
ContentView.swift:
Button(action: {/* other code */}) {
PackageManagerRow(packageManager: packageManagersData[0])
}
PackageManagerRow.swift:
struct PackageManagerRow : View {
var packageManager : PackageManager
var body: some View {
VStack {
HStack {
Image(packageManager.imageName)
.resizable()
.frame(width: 42.0, height: 42.0)
Text(verbatim: packageManager.name)
Spacer()
Image(systemName: "checkmark")
.foregroundColor(.blue)
.opacity(0)
}.padding(.bottom, 0)
Divider()
.padding(.top, -3)
}
}
}

I think this is from the rendering mode for the image you are using.
Where you have Image("Cydia logo") (or whatever).
You should be setting the rendering mode like...
Image("Cydia Logo").renderingMode(.original)

You can also add a PlainButtonStyle() to your button to avoid iOS style behaviors.
Something like that with your example :
Button(action: {/* other code */}) {
PackageManagerRow(packageManager: packageManagersData[0])
}.buttonStyle(PlainButtonStyle())
I hope it will help you!

Another option is to not use a Button wrapper, but instead use tapAction directly on the Image to trigger your action when the image is pressed

HStack {
Button(action: {
print("Tapped")
}, label: {
Image("Logo").renderingMode(.original) // Add Rendering Mode
})
Text("Cydia")
}

A button with an icon! How original 😀.
If you are dealing with SF symbols then the following will do fine:
Button(action: addItem) {
Text(Image(systemName: "plus").renderingMode(.original))
+
Text("This is Plus")
}
.font(.system(size: 42))
The limitation of the option above is you don't have control over Image's size. So for custom images the following is more appropriate:
Button(action: addItem) {
Label(
title: { Text("Label").font(.system(size: 40)) }, // Any font you like
icon: { Image(systemName: "rectangle.and.pencil.and.ellipsis") // Both custom and system images, i.e. `Image("Cydia logo")`
.renderingMode(.original)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 42, height: 42) // Any size you like
.padding() // Any padding you need
} // and etc.
)
}

Apply the style .plain to your button to avoid overlay color.
// Before
Button(...)
// After
Button(...)
.buttonStyle(.plain) // Remove the overlay color (blue) for images inside Button
.plain button style, that doesn’t style or decorate its content while idle, but may apply a visual effect to indicate the pressed, focused, or enabled state of the button.
Another solution is to custom the style with ButtonStyle
like: struct MyButtonStyle:ButtonStyle { }

You have to render the original image by adding .renderingMode(.original) right after your image declaration.
Image("your_image_name")
.renderingMode(.original)

Related

SwiftUI: Button with image got gray border on real iPhone but works fine on simulation mode

I'm trying to make simple app, the app seems working well on simulation mode, but test on my iPhone the button looks different from simulation iPhone,
link the simulation picture here,
enter image description here
this is the what I see on the simulation but real iPhone looks
enter image description here
please, check the pictures linked,
The button image made on sub View struct and called as a Button label,
plus I tried borderlessbuttonstyle, back and foreground color match both super, and child view. I got nothing, here the code,
I don't know how to delete the gray border not only these buttons but also all buttons that I used with image labeling looks the same way - bordered with gray rectangle,
here the button code
struct MainList: View {
#EnvironmentObject var pageControl: PageControl
var body: some View {
VStack{
Button{
pageControl.pageNum = 1
pageControl.detailPage = 0
print("button pressed pageNum: \(pageControl.pageNum), startPage \(pageControl.detailPage)")
} label: {
mainListRow(actuator: actuators[0])
.background(.white)
}
.buttonStyle(BorderlessButtonStyle())
.foregroundColor(.white)
and this is the View that used to button label
struct mainListRow: View {
var actuator: Actuator
var body: some View {
HStack(alignment: .center) {
Image("home")
Spacer()
VStack {
ZStack{
RoundedRectangle(cornerRadius: 10.0)
.frame(width: 60, height: 30)
.foregroundColor(.red)
Text(String(actuator.speed) + " %")
.foregroundColor(.white)
}
}
}
}
}
here add the another picture when I change the background color
enter image description here
as you can see that those three buttons, for the top button without background color, the button area is filled gray color, if I add background color - second button, it only filled inside, so I changed the background color to button field with black then the border color changed. thus I add the background color with white at button field not working - third button :(. the tested phone is iPhone Xs. this is really weird as I mentioned it work fine on simulation. please. test it on real iPhone.
here the code for third picture,
Button{
pageControl.pageNum = 1
pageControl.detailPage = 0
print("button pressed pageNum: \(pageControl.pageNum), startPage \(pageControl.detailPage)")
} label: {
mainListRow(actuator: actuators[0])
}
.buttonStyle(BorderlessButtonStyle())
Divider()
Button{
pageControl.pageNum = 1
pageControl.detailPage = 1
print("Rotate button pressed pageNum: \(pageControl.pageNum), startPage \(pageControl.detailPage)")
} label: {
mainListRow(actuator: actuators[1])
.background(.white)
}
.background(.black)
Divider()
Button{
pageControl.pageNum = 1
pageControl.detailPage = 2
print("Rotate button pressed pageNum: \(pageControl.pageNum), startPage \(pageControl.detailPage)")
} label: {
mainListRow(actuator: actuators[2])
.buttonStyle(BorderlessButtonStyle())
}
.background(.white)
and the the button page added to main here
struct testPage: View {
var body: some View {
MainList()
.frame(width: 330, height: 150)
.padding(.top, 20)
}
}
The gray/overlay on the button is from the button "tint".
To remove it, add the .plain modifier:
Button {
}
.buttonStyle(.plain)
As of today there is a bug in Xcode and that's why you can't see the tint properly in the simulator or the preview.
More examples of workarounds here:
https://hackingwithswift.com/quick-start/swiftui/how-to-disable-the-overlay-color-for-images-inside-button-and-navigationlink
in MainList get rid of:
.background(.white)

Background Img not staying on Screen Preview (SwiftUI w/ screen shots)

Xcode Version - 13.1
I'm having some issues with a Background Img & the grouping Form{}. What's happening is when placing a background image in a ZStack and include the grouping option, Form{}, the Background Img disappears.
Below my code showing my Background Image inside a ZStack. Also including a link to a screen shot of the Preview -> https://i.stack.imgur.com/u12Sw.png
var body: some View {
ZStack {
Image("login")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
}
}
However, when I add a Form{} inside the ZStack, the background image completely disappears and only the Form{} (w/ a TextField &SecureTextField) appears on the Preview Sans the Background Img. Below is the code w/ and a link to the Screen shot of the Preview -> https://i.stack.imgur.com/BI3U2.png
var body: some View {
ZStack {
Image("login")
.resizable()
.scaledToFill()
.edgesIgnoringSafeArea(.all)
Form {
TextField(
"Username (email)",
text: self.$username)
.autocapitalization(.none)
.disableAutocorrection(true)
SecureTextField(text: $password)
}
}
}
My assumption is that as long as the Form is inside the ZStack, the form should overlay the Background Img.
I like how the Form looks rather than two separate TextFields. Are there anyways to do this w/ the Form or something similar to a Form?
This is what I do at the moment:
I either add this modifier to the form.
.onAppear {
UITableView.appearance().backgroundColor = .clear
}
Or in an init() of the View
this has some side effects on the rest of the view if you have List or Forms but it works for me.

SwiftUI .contextMenu has visible padding around my View with a clipShape

I have an image in a chat bubble. This effect is achieved using a clipShape to a custom shape for the bubble.
When I attach a .contextMenu and present the contextMenu, it shows a padding around the chat bubble. Also the default cornerRadius of the contextMenu clips a small part of the chat bubble tip.
What I am trying to achieve is what I can see in the Apple Messages app. presenting contextMenu on a message with text preserves the clipShape of the text without adding padding. And on an image it removes the clipShape and resizes the image to a proper aspect Ratio. Neither of which I'm able to achieve.
struct ContentView: View {
var body: some View {
VStack {
Image("leaf")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 264, height: 361)
.clipShape(Bubble(location: .rightBottom))
.contentShape(Bubble(location: .rightBottom))
.contextMenu(ContextMenu(menuItems: {
Button {
} label: {
HStack {
Text("Save")
Image(systemName: "arrow.down.to.line")
}
}
}))
}
}
}
I have tried to use .contentShape to the same shape of the bubble but that had no effect at all. Is there anyway to show the contextMenu without the rounded corners and extra padding on the left side in screenshot below? Or to make the contextMenu containers background clear ideally?
Image prior to pressing context menu:
The solution was very simple. Just use content shape's first argument kind:
func contentShape<S>(_ kind: ContentShapeKinds, _ shape: S)
and set kind to .contextMenuPreview:
Image("leaf")
.resizable()
.aspectRatio(contentMode: .fill)
.frame(width: 264, height: 361)
.clipShape(Bubble(location: .rightBottom))
.contentShape(.contextMenuPreview, Bubble(location: .rightBottom))
.contextMenu(ContextMenu(menuItems: {
Button {
} label: {
HStack {
Text("Save")
Image(systemName: "arrow.down.to.line")
}
}
}))

SwiftUI - Detect taps in a disabled SecureField?

I am writing an iOS app using SwiftUI. As part of the data presented, I need to have a password field with a show/hide button that toggles between hidden/visible. To do this, I'm using a combination of SwiftUI's SecureField and TextField controls. Both are in the disabled state to prevent users from editing, as I want this field to be read-only. This setup is working perfectly. (See screenshot below)
Now I would like to add functionality that would allow the user to copy the password to the clipboard just by tapping on it. I've added an onTapGesture block to my SecureField, however it is never called. I believe this is due to the fact that my SecureField/TextField is marked as disabled. A snippet of my code follows:
ZStack(alignment: .trailing) {
if isSecured {
VStack {
SecureField("", text: Binding.constant(text))
.disabled(true)
.onTapGesture {
print("USER TAPPED!")
}
}
} else {
TextField("", text: Binding.constant(text))
.disabled(true)
}
Image(systemName: self.isSecured ? "eye" : "eye.slash")
.accentColor(.gray)
.foregroundColor(.gray)
.frame(width: 20, height: 20, alignment: .center)
.modifier(TouchDownUpEventModifier(changeState: { (buttonState) in
withAnimation {
self.isSecured.toggle()
}
}))
}
Is there a way to accomplish this? I want my SecureField/TextField to be read-only but still detect when a user taps the control?
I was able to solve this by adding the following modifiers on my ZStack:
.contentShape(Rectangle())
.onTapGesture {
...
}
Once I did this, my ZStack began intercepting the touch events and I could perform the desired action.

SwiftUI | customize dragitem appearance within LazyGrid using onDrag [duplicate]

I've been wondering if there is any way to customize the preview image of the view that's being dragged when using onDrag?
As you might see, the preview image is by default a slightly bigger opacity image of the view.
From what I have found, a preview image is generated at the very beginning of the dragging process. But I couldn't find a way to change it.
What I mean by customizing is to have some custom image or a preview image of a custom view. (Both without the default opacity)
Does anyone have an idea?
I have tried to use previewImageHandler and in general, read a lot about the NSItemProvider. But for me, it seems like this is something that is not possible for SwiftUI yet?
With UIKit one could have just customized the UIDragItem - something like that using previewProvider: Here
Here is my demo code:
struct ContentView: View {
var body: some View {
DraggedView()
.onDrag({ NSItemProvider() })
}
private struct DraggedView: View {
var body: some View {
RoundedRectangle(cornerRadius: 20)
.frame(width: 120, height: 160)
.foregroundColor(.green)
}
}
}
I will use this for drag and drop within a LazyVGrid, so custom gestures are unfortunately no option.
One second idea I had would be to have a gesture simultaneously that first changes the item to be dragged to something else and then onDrag starts and returns the NSItemProvider with the preview image which would be then the one I would want. But I couldn't have those two gestures go at the same time, you would have to dismiss one first in order to start the second.
Thank you!
iOS 15 adds an API to do this - you can specify the View to use for the preview. onDrag(_:preview:)
RoundedRectangle(cornerRadius: 20)
.frame(width: 120, height: 160)
.foregroundColor(.green)
.onDrag {
NSItemProvider()
} preview: {
RoundedRectangle(cornerRadius: 18)
.frame(width: 100, height: 140)
.foregroundColor(.green)
}

Resources