swiftswiftui

Placing background image and text/icon in a button in SwiftUI?


I'm trying to achieve something like this in SwiftUI:

enter image description here

I can create the button with rounded corners and I can also place the text in the center of it but that's about it.

I need to be able to place background image and the footer title/text and the icon at the top!

This is what I have so far:

                            Button(action: {
                                    //self.dismiss?()
            
                                    
                                })
                                {
                               

                                    HStack(spacing: 10) {
                                        Image(systemName: "pencil").renderingMode(.template)
                                        Text(audio.name)
                                            //.font(.headline)
                                            .frame(width: 160, height: 200)
                                        
                                            .background(Color.gray)
                                            .addBorder(Color.white, width: 1, cornerRadius: 10)
                                    }
                                    
                    
                                    
                                }

When I try my code, I get a button with rounded corner and the text in the middle but the Image(systemName: "pencil") is outside of the button for some reason!

Can someone please guide me how to achieve this?

It might be worth mentioning that the background images are coming from a remote server.


Solution

  • First, to handle the backend that loads the image, create an Observable object that handles states and networking.

    import Combine
    
    
    
    class ImageManager: ObservableObject {
    
        // Property that fires an update onto the interface when it has any changes to its value
        @Published var image: UIImage? = nil
    
        init(link: String) {
            // Standard way of loading data from a link.
            guard let url = URL(string: link) else { return }
            let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
                if let error = error {
                    // handle the error here !
                    print(error)
                } else if let data = data {
                    // Never forget to update on the main thread ! 😁
                    DispatchQueue.main.async {
                        self.image = UIImage(data: data)
                    }
                }
            }
            task.resume()
        } 
    }
    

    Second, build the Image view that handles the communication with the Observable object as it follows :

    struct ImageLoader: View {
    
         // The property wrapper '@ObservedObject' makes our ImageLoader change its interface according to updates received from our ImageManager
         @ObservedObject private var manager: ImageManager
    
         init(link: String) {
             manager = ImageManager(link: link)
         }
    
         var body: some View {
             Image(uiImage: manager.image ?? UIImage())
                .resizable()
                .aspectRatio(contentMode: .fill)
                // Make the view greyish as long there is no image loaded to present
                .redacted(reason: manager.image == nil ? .placeholder:[])
        }
    }
    

    And third, build the final view. You can use overlay to add views on top of your card as well as a

    .frame(maxWidth:, maxHeight, alignment:)

    to build your overlay views efficiently without having too many additional views. 😉

    struct MovieCard: View {
    
        @State var thumbnailLink = "https://media.senscritique.com/media/000010045541/source_big/Tomb_Raider.png"
    
         var body: some View {
            ImageLoader(link: thumbnailLink)
                .frame(width: 200, height: 200)
                // Overlay is a very simple way to add content on top of your view ! 😁
                .overlay(
                     VStack {
                         // Using the system heart icon see https://developer.apple.com/sf-symbols/
                         Image(systemName: "suit.heart.fill")
                            .font(.title3)
                            .padding()
                            // Using maxWidth, maxHeight and alignement is usefull to not use additionnal views in your code (such as spacers, stacks, etc…)
                            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                            .foregroundColor(.red)
                    
                         Text("Movie title")
                            .font(Font.body.bold())
                            .foregroundColor(.white)
                            .padding()
                            .frame(maxWidth: .infinity)
                            .background(Color.red.opacity(0.7))
                    }
                )
                .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
                .shadow(color: Color.black.opacity(0.3), radius: 6, x: 0, y: 3)
                .onTapGesture {
                    // Handle the action when the card is touched
                }
            }
        }
    

    enter image description here