I'm trying to achieve something like this in SwiftUI:
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.
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
}
}
}