I’m making an attempt to construct a widget for my app to point out countdowns. Within the background, it ought to have a picture chosen by the person. All the things within the widget works largely fantastic besides that the picture isn’t filling the complete widget. Initially, I assumed the ZStack was solely taking as a lot house because the VStack with the textual content wanted, however I’ve tried commenting the VStack and it nonetheless has the house round it.
I’ve tried asking ChatGPT, Claude and Gemini, however all they inform me is so as to add .scaledToFill(), .body(maxWidth: .infinity, maxHeight: .infinity), or .ignoresSafeArea(), however these do not appear to work.
If I take away the .resizable(), the picture will get means greater than the widget.
That is the code I’ve:
struct SimpleEntry: TimelineEntry {
let date: Date
let configuration: ConfigurationAppIntent
}
let backgroundGradient = LinearGradient(
colours: [Color.red, Color.blue],
startPoint: .high, endPoint: .backside)
struct CountdownsEntryView : View {
var entry: Supplier.Entry
func daysLeftText(days_left: Int) -> String {
var days_left_text: String = "(days_left) days left"
if days_left == 1 {
days_left_text = "(days_left) day left"
}
else if days_left == 0 {
days_left_text = "In the present day!"
}
else if days_left < 0 {
days_left_text = "(abs(days_left)) days in the past"
}
return days_left_text
}
var physique: some View {
ZStack {
// Background picture
if let widgetImg = entry.configuration.countdown?.picture,
let uiImg = UIImage(knowledge: widgetImg) {
Picture(uiImage: uiImg)
.resizable()
.scaledToFill()
} else {
// Fallback gradient if no picture
LinearGradient(
colours: [Color.purple, Color.blue],
startPoint: .high,
endPoint: .backside
)
}
// Textual content overlay
VStack(spacing: 30) {
Textual content(entry.configuration.countdown?.title ?? "Default")
.foregroundStyle(.white)
.font(.largeTitle)
.minimumScaleFactor(0.01)
.lineLimit(1)
.shadow(
colour: Colour.major.opacity(0.5), /// shadow colour
radius: 3, /// shadow radius
x: 0, /// x offset
y: 2 /// y offset
)
Textual content(daysLeftText(days_left: daysLeft(date: entry.configuration.countdown?.date ?? Date())))
.foregroundStyle(.white)
.font(.title)
.minimumScaleFactor(0.01)
.lineLimit(1)
.shadow(
colour: Colour.major.opacity(0.5), /// shadow colour
radius: 3, /// shadow radius
x: 0, /// x offset
y: 2 /// y offset
)
}
.padding()
}
}
}
struct Countdowns: Widget {
let sort: String = "Countdowns"
var physique: some WidgetConfiguration {
AppIntentConfiguration(sort: sort, intent: ConfigurationAppIntent.self, supplier: Supplier()) { entry in
CountdownsEntryView(entry: entry)
.containerBackground(.fill, for: .widget)
// .background(backgroundGradient)
}
}
}
When including the picture to the app, that is how I course of it:
extension UIImage {
func croppedToSquare() -> UIImage {
// If picture is already sq., return as-is
if measurement.width == measurement.peak {
return self
}
// Decide the aspect size (use the smaller dimension)
let sideLength = min(measurement.width, measurement.peak)
// Calculate the crop rectangle (centered)
let xOffset = (measurement.width - sideLength) / 2
let yOffset = (measurement.peak - sideLength) / 2
let cropRect = CGRect(x: xOffset, y: yOffset, width: sideLength, peak: sideLength)
// Crop the picture
guard let cgImage = self.cgImage,
let croppedCGImage = cgImage.cropping(to: cropRect) else {
return self
}
return UIImage(cgImage: croppedCGImage, scale: self.scale, orientation: self.imageOrientation)
}
func resizedForWidget(maxWidth: CGFloat = 400) -> UIImage {
// First crop to sq.
let squareImage = croppedToSquare()
// If already sufficiently small, return as-is
if squareImage.measurement.width <= maxWidth {
return squareImage
}
// Resize to maxSize
let newSize = CGSize(width: maxWidth, peak: maxWidth)
let renderer = UIGraphicsImageRenderer(measurement: newSize)
return renderer.picture { _ in
squareImage.draw(in: CGRect(origin: .zero, measurement: newSize))
}
}
// Various technique with extra aggressive compression for widgets
func optimizedForWidget() -> UIImage {
// Resize to most 300px for widgets (extra conservative)
let resized = resizedForWidget(maxWidth: 300)
// Convert to JPEG and again to cut back file measurement
guard let jpegData = resized.jpegData(compressionQuality: 0.8),
let compressedImage = UIImage(knowledge: jpegData) else {
return resized
}
return compressedImage
}
}
And that is the way it appears to be like within the widget:

Am I lacking one thing? May anybody assist me with this?
Thanks.

