SwiftUI Changing an Image in a Button

Goal: Have a button, with an image (system image) in it. User taps, button, selects image from photo library, and button now appears with selected image.
I have the code to select the Image from UIImagePicker. I can even get the code so that the image updates another area in the VStack (like the apple tutorial and as Brian Advent shows).

ISSUE:
However, I can’t get the button to update it’s self.
I get either a Blue square where the button is and not the image, or I get that false error “Int is not convertible to CGFloat”
I’ve tried using .aspectRation(1, contentMode: .fit) and a multitude of other things, yet can NOT get this to work.

Code:
CODE THAT WORKS:
import SwiftUI
struct ContentView: View {
@State var showingImagePicker = false
@State var image: Image? = nil

var body: some View {
    VStack{
        if (image == nil) {
            Image(systemName: "square.and.arrow.down.fill")
                .resizable()
                .frame(width: 150, height: 150)
            Text("No Image specified\n      Select Image")
        }
        else {
            image?
            .resizable()
            .frame(width: 150, height: 150)
        }
            
        Button("choose Image") {
                self.showingImagePicker.toggle()
        }
            
    }.sheet(isPresented: $showingImagePicker, content: {
        ImagePicker.shared.view
    }).onReceive(ImagePicker.shared.$image) {
        image in self.image = image
    }

CODE THAT DOES NOT WORK
import SwiftUI
struct ContentView: View {
@State var showingImagePicker = false
@State var image: Image? = nil

var body: some View {
    VStack{
        
        Button(action: {
            if (self.image == nil) {
                self.showingImagePicker.toggle()
            }
            else {
                print("Image Selected")
                //New_more_other code here
            }
        })
        {
            if (image == nil) {
                VStack {
                    Image(systemName: "square.and.arrow.down.fill")
                        .resizable()
                        .frame(width: 150, height: 150)
                    Text("No Image specified\n      Select Image")
                }
            }
            else {

/*THIS IS THE SECTION I GET THE ERROR: can’t convert int to cgfloat
Image(uiImage: image)
.resizable()
.frame(width: 150, height: 150)
//.padding(.bottom)
*/
}
}
}.sheet(isPresented: $showingImagePicker, content: {
ImagePicker.shared.view
}).onReceive(ImagePicker.shared.$image) {
image in self.image = image
}
}
}

I’ve been at this for weeks now, and have become extremely frustrated with it and I’m about to give up on swiftUI and just write it off as an unuseable language. So any help would be appreciated

Ok, even got this to work:
var body: some View {
VStack{
if (image == nil) {
Button(action: {
self.showingImagePicker.toggle()
}) {
VStack {
Image(systemName: “square.and.arrow.down.fill”)
.resizable()
.frame(width: 150, height: 150)
Text(“No Image specified\n Select Image”)
}
}
}
else {
image?
.resizable()
.frame(width: 150, height: 150)
/*Trying to add another button here doesnot work: Errors all over the place
Button(action: {
print(“SwiftUI is a piece of JUNK!”)
})
{
VStack {
Image(uiImage: image)
.frame(width:150, height: 150)
}
}
*/
}

One issue I can see right away is this line:

Image(uiImage: image)

Your image variable is not a uiImage but rather an Image, as can be seen in the declaration line:

@State var image: Image? = nil

So you are trying to initialize a SwiftUI Image from another Image but using the uiImage initializer.

I don’t know if that will fix your whole problem but it’s a start.

thanks for the call out on that!
I tried just using Image (i.e. not casting it), as well as casting it from a uiImage to an Image,
and neither worked. I got some other strange errors when doing that.
Image(image)–>doesn’t work
Image(uiImage: (image as uiImage))–>doesn’t work
Image(uiImage: image) as UIImage–>doesn’t work

at this point it appears you can NOT set a custom image into a button image.

I’m betting the issue lies more with your code than in an inability of SwiftUI to do what you’re trying to do. I’d start figuring it out by checking your assumptions about your variable types, particularly what you’re getting back from the ImagePicker.

It would also help diagnose your problem if you posted more of your code, again particularly the ImagePicker object you are using to get an image.

(And, as a side note, please format your code better so it’s easier to read. Don’t rely on the forum automatically formatting it because it won’t get every line; instead use the triple backtick explicit code formatting as explained in point 5 here.)

Appreciate the help. Here’s the code in a better formatted way:
ContentView.swift file:

import SwiftUI
import UIKit

struct ContentView: View {
    @State var IsShowingImages = false
    @State var Button1Image = UIImage()
    @State var ImageSelectedFlag = false
    
    @State var showingImagePicker = false
    @State var image: Image? = nil
    
    var body: some View {
        VStack{
            if (image == nil) {
                Button(action: {
                    self.showingImagePicker.toggle()
                })
                {
                    VStack {
                        Image(systemName: "square.and.arrow.down.fill")
                            .resizable()
                            .frame(width: 150, height: 150)
                        Text("No Image specified\n      Select Image")
                    }
                }
            }
            else {
                /*
                Button(action: {
                 print("some text")
                }){
                    VStack {
******              Image(ImagePicker.shared.image)<—Error area: If I uncomment this: I get the error: generic parameter 'FalseContent' could not be inferred
                                                                                                           OR the int can’t be converted to CGFloat error.
                        .frame(width: 150, height: 150)
                    }
                }*/
                
                image?
                .resizable()
                .frame(width: 150, height: 150)
            }                
        }.sheet(isPresented: $showingImagePicker, content: {
            ImagePicker.shared.view
        }).onReceive(ImagePicker.shared.$image) {
            image in self.image = image
            }
      }
}

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

As stated: I’ve tried the following in that one line that I have marked:
Image(image)
Image($image)
Image(image?)
Image(image!)
Image(uiImage: image)
Image(uiImage: (image as uiImage))
Image(uiImage: image) as UIImage
Image(uiImage: self.image)
Image(uiImage: self.$image)
Image(ImagePicker.shared.$image)
Image(Image.image[0])

Nothing works.

Here’s the code for the ImagePicker:

import Foundation
import SwiftUI
import Combine

class ImagePicker : ObservableObject {
    static let shared: ImagePicker = ImagePicker()
    private init() {}
    
    let view = ImagePicker.View()
    let coordinator = ImagePicker.Coordinator()
    
    let willChange = PassthroughSubject<Image?, Never>()
    @Published var image:Image? = nil {
        didSet {
            if (image != nil) {
                willChange.send(image)
            }
        }
    }
}
extension ImagePicker {
    class Coordinator: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true)
        }
        
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
            ImagePicker.shared.image = Image(uiImage: image) //It seems to be setting the image to a 
                                                             //UIImage type right here
            picker.dismiss(animated: true)
        }
    }
}

extension ImagePicker {
    struct View: UIViewControllerRepresentable {
        func makeCoordinator() -> Coordinator {
            return ImagePicker.shared.coordinator
        }
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<ImagePicker.View>) -> UIImagePickerController {
            let imagePickerController = UIImagePickerController()
            imagePickerController.delegate = context.coordinator
            return imagePickerController
        }
        
        func updateUIViewController(_ uiViewController: UIImagePickerController, context: UIViewControllerRepresentableContext<ImagePicker.View>) {
        }
    }
}

I’ve Tried changing the variable type to UIImage through outt this code and get nothing but a bunch of errors as well.

As stated I have been at this for over three weeks, searching and posting and reading, and can not find one answer that works. I’m very frustrated with SwiftUI and am about to give up on it.

ImagePicker.shared.image is a SwiftUI Image view. So you shouldn’t be trying to initialize an Image from it; it’s already an Image. That’s what I meant about checking your variable types.

This line in ImagePicker.imagePickerController constructs your SwiftUI Image view:

ImagePicker.shared.image = Image(uiImage: image)

So replace this line:

Image(ImagePicker.shared.image)

with this:

image

And your code should compile without errors.

OK, took a little work to actually get it working as you desired after taking care of the errors, but here we go.

There were two problems I had to fix:

First, the image didn’t show up on the button after I selected it. I solved this by adding .resizable() just before setting the frame.

Second, the button showed a solid blue square instead of the image. This was caused by the default buttonStyle in SwiftUI, so I solved it by adding .buttonStyle(PlainButtonStyle()).

So your final button should look like this:

Button(action: {
    print("some text")
}){
    VStack {
        image!  // we can force unwrap the image because we already checked if image == nil
            .resizable()
            .frame(width: 150, height: 150)
    }
}.buttonStyle(PlainButtonStyle())
2 Likes

AWESOME!!!
Thank you SOOOOOOOO MUCH!!!
I had been fighting with that for weeks.
I actually got it to the same point you did with the blue square in the button but couldn’t figure out what to do from there.
So now I have one final question:
How were you able to figure out that it was the .buttonStyle(PlainButtonStyle()) member that was the issue?

Again, Thank you very much!

It took me a while but I eventually realized, hey, it’s a button and buttons are usually displayed in blue text so maybe an image as a button would be in blue too. (Although, to be honest, I would have expected it to be outlined in blue rather than a solid color.) Then I just looked up how to change the style of a SwiftUI button.