Image resizing makes Button unclickable - image

I have a simple scrollview with a SDWebImage pulling in Firebase URL links to display images. For some bizarre reason, when I do not give a specific frame to my images, I can click on the button positioned in the bottom left corner. But when I give it a max height or any height at all, the button becomes unclickable. This seriously might be the weirdest SwiftUI issue I have ever seen - any help would be great.
import SwiftUI
import SDWebImageSwiftUI
struct Test2FeedView: View {
#StateObject var viewmodel = FeedViewModel()
#State var show : Bool = false
var body: some View {
ScrollView{
VStack(spacing:5) {
ForEach(viewmodel.posts){ post in
ZStack(alignment:.bottomLeading) {
WebImage(url: URL(string: post.original_posted_image))
.resizable()
.scaledToFill()
.frame(maxHeight:440)
.clipped()
Button(action: {
show.toggle()
}){
Text("PRESS ME")
}
}
}
}
}.sheet(isPresented: $show) {
Text("hey")
}
}
}

The Button becomes unclickable because the result of .clipped() is only visual — in reality, the image spans all the way 'above' the button, thus is being used for hit testing. You should use .allowsHitTesting(false) to disable that.
WebImage(url: URL(string: post.original_posted_image))
.resizable()
.scaledToFill()
.frame(maxHeight:440)
.clipped()
.allowsHitTesting(false) // <- here

try putting the .frame(maxHeight:440) just before .scaledToFill(), works for me.

Related

Background color broken in NavigationView in iOS 16 - SwiftUI

Prior to iOS 16, screen background in a NavigationView used to work very reliably in SwiftUI.
In iOS 16, there is a hollow space (without background color) when navigating back and forth with on-screen-keyboard:
Repro code:
import SwiftUI
struct ContentView: View {
#State private var text: String = ""
var body: some View {
NavigationView {
VStack {
TextField("Edit field", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
NavigationLink(destination: Text("Naviagtion target")) {
HStack {
Image(systemName: "link")
Text("Navigation Link")
}
}
.padding(30)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color.gray)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Any ideas how to fix this?
It does look and feel like a bug, where the safe area that’s adjusted when the keyboard appears doesn’t re-adjust when you navigate back and the keyboard’s not there.
One way round the bug is to adjust the background’s idea of what the safe area is, by telling it to always reclaim the keyboard space:
.background(
Color.gray
.ignoresSafeArea(.keyboard)
)
Because you’re only adjusting the safe area for the background, the rest of your view should continue to behave as before. That means that it’s still elevated to accommodate the (missing) keyboard’s space requirement, but it’s less obvious.

How do I indicate to swiftUI ScrollView that content size has changed?

I am writing a swiftUI app for macOS.
Consider this situation: I have a scroll view containing an image that is resizable. If I resize the window to smaller than the image, the scroll views bars appear as expected. If I use .scaleEffect(someStateVar) to resize the image (by changing someStateVar), the scroll view content does not update to the new size of the image.
Other than wrapping NSScrollView into a swiftUI view, how do I indicate to the swiftUI scrollview that the content size has changed?
Example that shows scaling the image does not set the "content size" of the scrollview. When you size the clock to large than the scroll view, the scroll bars do not appear and you cannot scroll. When you try to scroll, you briefly see the hidden areas but the view jumps back to the original location.
import SwiftUI
struct MyImageView: View {
#State var scale: CGFloat = 1
var theImage: some View{
ScrollView([.horizontal, .vertical]){
VStack{
HStack{
Image(systemName: "clock.fill")
.scaleEffect(scale)
}
}
}
}
var body: some View {
theImage
Slider(value: $scale, in: 1...1000)
}
}
struct ContentView: View {
var body: some View {
MyImageView()
}
}
You can use the .frame modifier to explicitly set the size of the VStack. The downside is that you have to know the full dimensions.
var theImage: some View{
ScrollView([.horizontal, .vertical]){
VStack{
HStack{
Image(systemName: "clock.fill")
.scaleEffect(scale)
}
}
.frame(width: 50 * scale, height: 50 * scale, alignment: .center)
}
}

In SwiftUI, how to scale content inside scrollView with dynamic changing scrolling area?

I'm developing MacOS App with SwiftUI.
Let's say we have a fixed size window, which will show a scrollable image.
Now I want to create a feature which can let user to scale the image, and can scroll and view the scaled image inside the window.
The problem is, once I scaled up the image, the scroll area seems not expended together, and the scroll area is not big enough to reach each corner of the image.
struct ContentView: View {
var body: some View {
ScrollView([.horizontal, .vertical] , showsIndicators: true ){
Image("test")
.scaleEffect(2)
}
.frame(width: 500, height: 500)
}
}
I have tried to set the Image's frame by using GeometryReader, but got the same result.
MacOS 11.1, Xcode 12.3
Thanks!
.scaleEffect seems to perform a visual transform on the view without actually affecting its frame, so the ScrollView doesn't know to accommodate the bigger size.
.resizable() and .frame on the image seem to do the trick:
struct ContentView: View {
#State private var scale : CGFloat = 1.0
var body: some View {
VStack {
ScrollView([.horizontal, .vertical] , showsIndicators: true ){
Image(systemName: "plus.circle")
.resizable()
.frame(width: 300 * scale,height: 300 * scale)
}
.frame(width: 500, height: 500)
Slider(value: $scale, in: (0...5))
}
}
}

SwiftUI Touchable Area of a Button

I am trying to increase the touchable area of a button inside a NavigationView. It does not work even though the area is made bigger. My code is below:
var body: some View {
NavigationView {
List(taskStore.tasks) { tasks in
Text(tasks.name)
}
.navigationBarTitle("Tasks")
.navigationBarItems(
trailing: Button(action: {
self.modalIsPresented = true
}){
Image(systemName: "plus")
.frame(width: 200, height: 200)
.contentShape(Rectangle())
.background(Color.yellow)
})}
The green area is touchable and the red area isn't touchable.
I found a solution online that works. However this solution only works for a button that is NOT in the NavigationView. So if I put the button in "some view" like the following below, it works as per the solution:
var body: some View {
Button(action: {self.modalIsPresented = true} ) {
Text("Default padding")
.padding(50)
.background(Color.yellow)
}}}
But when I put the button in a Navigation View like my code, the yellow area is not touchable. How can I get the whole yellow area (red box) to be touchable like the solution?
Thanks :D
Example of solution:
If you want a button in the navigation bar, it is only going to be clickable inside the navigation bar, no matter what you try to set the image's frame at, and the NavigationView determines that height, no matter what the children- the button, in this case- may want.
Historically, changing the height of the NavigationBar has not been supported: see the comments here
Now you could do something funky with ZStacks- put a button on top of the navigation view, perhaps- but you're not going to be able to put anything larger than the set height inside the navigation bar.
I think you may find your desired effect inside the .navigationBarItems(trailing:) if you use .contentShape(Rectangle()) modifier. Or you could use other shapes to suit your needs. Then adjust the size of the .frame to adjust the tappable area as desired. Here is a quick code example.
struct ContentView: View {
var body: some View {
NavigationView {
List {
Text("Hello, World")
}
.navigationBarTitle(Text("Items"))
.navigationBarItems(trailing: Button(action: {
print("Do Something")
}, label: {
Image(systemName: "plus")
.frame(width: 100, height: 50)
.contentShape(Rectangle())
.border(Color.red, width: 3)
.background(Color.gray)
}))
}
}
}
I hope this helps.

Issue with Buttons in SwiftUI on MacOS

I'm using SwiftUI on MACOS
If I do this:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
I get this:
and the two grey areas are the ends of a tappable button.
but I would expect the button to be the shape of the blue area.
Any ideas how I can get the whole blue area to be tappable.
(I did look at using .onTapGesture but this doesn't animate the button so that you know you've tapped it.)
You can achieve the look you want by using a ButtonStyle and then specifying colors and other style attributes based on the configuration values being passed in.
It would be nice if there was a happy medium where you could inherit the default button radius, automatic width based on the text length and other attributes, but at least there is the ability to specify all the attributes and get the look you want.
Hope this helps!
import SwiftUI
struct BlueButtonStyle: ButtonStyle {
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.foregroundColor(configuration.isPressed ? Color.blue : Color.white)
.background(configuration.isPressed ? Color.white : Color.blue)
.cornerRadius(6.0)
.padding()
}
}
struct ContentView: View {
var body: some View {
VStack {
Text("Hello World")
.frame(maxWidth: .infinity, maxHeight: .infinity)
Button(action: {
}) {
Text("Press")
.frame(maxWidth: 100, maxHeight: 24)
}
.buttonStyle(BlueButtonStyle())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Inspired by #Gene Z. Ragan 's great answer I've started with that answer and taken this a bit further:
Making the ButtonStyle a bit more flexible:
struct NiceButtonStyle: ButtonStyle {
var foregroundColor: Color
var backgroundColor: Color
var pressedColor: Color
func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
.font(.headline)
.padding(10)
.foregroundColor(foregroundColor)
.background(configuration.isPressed ? pressedColor : backgroundColor)
.cornerRadius(5)
}
}
and then some sugar to make it cleaner at the call site:
extension View {
func niceButton(
foregroundColor: Color = .white,
backgroundColor: Color = .gray,
pressedColor: Color = .accentColor
) -> some View {
self.buttonStyle(
NiceButtonStyle(
foregroundColor: foregroundColor,
backgroundColor: backgroundColor,
pressedColor: pressedColor
)
)
}
}
then means we can use default colouring:
white foreground, grey background and accentedColor pressedColor
Button(action: { } ) {
Text("Button A")
}
.niceButton()
or we can customise the colours:
Button(action: { } ) {
Text("Button B has a long description")
}
.niceButton(
foregroundColor: .blue,
backgroundColor: .yellow,
pressedColor: .orange
)
And we get:
Thanks again Gene.
I raised this with Apple on the Feedback assistant to see if they had any useful thoughts.
Dialogue here:
I said:
If on MACOS with SwiftUI I do this:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
I get a blue box with two grey bits sticking out the sides.
Image attached.
These are the two grey areas are the ends of a tappable button. but I would expect the button to be the shape of the blue area.
The same code works fine as expected on iOS.
(I did look at using .onTapGesture but this doesn't animate the button so that you know you've tapped it.)
Apple said:
iOS and macOS have different default ButtonStyles — iOS is borderless, macOS has that standard bezel effect.
It sounds like you’re trying to create a custom ButtonStyle (and so not have any system provided chrome). So you’ll want to create that, which can apply that blue background to the label of the button, and then apply that to your simple button with Text, e.g.
Button("Press"), action {}).buttonStyle(BluePaddingButtonStyle()
This will ensure that it has the same appearance on every platform you run it on.
I said:
Hi, Thanks for the explanation.
I get what you are saying and I’m ok with the method I’ve come up with.
It still doesn’t seem right that:
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
should produce something so odd looking.
I don’t understand why that bit of code couldn’t just produce a blue button.
As I say - it’s not a problem because I’ve worked around it but it
currently doesn’t seem intuitive.
Apple said:
The provided content inside the ViewBuilder is used as the label of the button: not the entire button. The button will still come with the surrounding background, bezel, foreground styling, etc as described by it’s ButtonStyle. So if your button needs to have a very specific appearance, then it needs to customize that style: either to the BorderlessButtonStyle (though note that still does come with a specific foreground appearance style), or to a custom ButtonStyle.
My thoughts:
This did help me understand why it shows as it does but intuitively it still seems wrong !!!
You can "force" an iOS-like behavior on macOS by adding .buttonStyle(.borderless).
Button(action: { } ) {
Text("Press")
.padding()
.background(Color.blue)
}
.buttonStyle(.borderless)
I don't think this is possible but you could try using this
Supports SPM, is build for Swift 5.1 and is lean
In Swift 5, this could be achieved with below simple code -
Button("First Button") {
print("Hello World")
}
.padding(10)
.accentColor(.yellow)
.background(Color.blue)
.cornerRadius(10)

Resources