0

I am learning how to create a widget for the first time. I have finished the design and everything is good to go and I am ready to put it on my app. However, I found a problem that still needs to be addressed. I am making a countdown that updates every second. While updating, it writes brand new images every second and builds up the memory, while the disk shows 20-30mb per second and the CPU is stuck at 90-95%. How can I simply update the label without updating the image?

Here my main code:

struct Provider: IntentTimelineProvider {
    func placeholder(in context: Context) -> SimpleEntry {
        SimpleEntry(date: Date(), configuration: ConfigurationIntent())
    }

    func getSnapshot(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (SimpleEntry) -> ()) {
        let entry = SimpleEntry(date: Date(), configuration: configuration)
        completion(entry)
    }

    func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []

        let currentDate = Date()
        let entryDate = Calendar.current.date(byAdding: .second, value: 1, to: currentDate)!
        let entry = SimpleEntry(date: entryDate, configuration: configuration)
        entries.append(entry)

        let timeline = Timeline(entries: entries, policy: .atEnd)
        
        let _ =  Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { (timerSec) in
            WidgetCenter.shared.reloadAllTimelines()
        }
        
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let configuration: ConfigurationIntent
}

struct Widget_Practice_WidgetEntryView : View {
    @Environment(\.widgetFamily) var family
    var entry: Provider.Entry
    
    var body: some View {
        switch family {
        case .systemSmall: CreateSmallDesign()
        case .systemMedium: CreateMediumDesign()
        case .systemLarge: CreateLargeDesign()
        case .systemExtraLarge: CreateExLargeDesign()
        @unknown default: CreateSmallDesign()
        }
    }
}

@main
struct Widget_Practice_Widget: Widget {
    let kind: String = "Widget_Practice_Widget"

    var body: some WidgetConfiguration {
        IntentConfiguration(kind: kind, intent: ConfigurationIntent.self, provider: Provider()) { entry in
            Widget_Practice_WidgetEntryView(entry: entry)
        }
        .configurationDisplayName("Edit Widget")
        .description("This is an example widget.")
    }
}

struct Widget_Practice_Widget_Previews: PreviewProvider {
    static var previews: some View {
        Widget_Practice_WidgetEntryView(entry: SimpleEntry(date: Date(), configuration: ConfigurationIntent()))
            .previewContext(WidgetPreviewContext(family: .systemSmall))
    }
}

Here my code for medium size view:

struct CreateMediumDesign : View {
    var body: some View {
        ZStack {
            Image(uiImage: resizeImage(image: UIImage(named: demoCountdownDatabase[1].imageName), targetSize: CGSize(width: 250, height: 250))!)
                .resizable()
                .scaledToFill()
                .blur(radius: 30)
            
            VStack {
                HStack(alignment: .center, spacing: 15) {
                    Text("")
                    Image(uiImage: resizeImage(image: UIImage(named: demoCountdownDatabase[1].imageName), targetSize: CGSize(width: 250, height: 250))!)
                        .resizable()
                        .scaledToFill()
                        .frame(width: 80, height: 80, alignment: .center)
                        .clipShape(RoundedRectangle(cornerRadius: 15))
                        .overlay(RoundedRectangle(cornerRadius: 15).stroke(Color.white, lineWidth: 2))
                        .shadow(radius: 10)
                    VStack(alignment: .leading) {
                        Text(demoCountdownDatabase[1].title)
                            .font(.largeTitle)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .scaledToFill()
                        Text(demoCountdownDatabase[1].subtitle)
                            .font(.body)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .scaledToFill()
                    }
                    Spacer()
                }
                
                GeometryReader { geometry in
                    HStack(alignment: .center, spacing: 0) {
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Year"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Month"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Day"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Hour"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Minute"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                        Text(fullDateUntilString(date: demoCountdownDatabase[1].toDate, title: "Second"))
                            .font(.footnote)
                            .frame(width: geometry.size.width / 6.1, height: 35)
                            .foregroundColor(.white)
                            .shadow(color: .black, radius: 2, x: 0, y: 0)
                            .multilineTextAlignment(.center)
                    }
                }
                .frame(height: 15)
            }
        }
    }
}

Here is my code to resize an image to reduce its size:


func resizeImage(image: UIImage?, targetSize: CGSize) -> UIImage? {
    if image != nil {
        let size = image!.size

        let widthRatio  = targetSize.width  / size.width
        let heightRatio = targetSize.height / size.height

        var newSize: CGSize
        if(widthRatio > heightRatio) {
            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
        } else {
            newSize = CGSize(width: size.width * widthRatio,  height: size.height * widthRatio)
        }
        
        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)

        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
        image!.draw(in: rect)
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return newImage ?? nil
    } else {
        return nil
    }
}

UPDATE: I wanted to make it clear that I was trying to reload only the text, and not the image. Refreshing every second worked perfectly for me, however I want it to ignore the image when reloading / refreshing. Focusing on text only is necessary since reloading every second causes the image to reload every second, which causes hardware to crash.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Travgalax
  • 81
  • 1
  • 7
  • Does this answer your question? [Updating time text label each minute in WidgetKit](https://stackoverflow.com/questions/64053733/updating-time-text-label-each-minute-in-widgetkit) – lorem ipsum Nov 28 '21 at 02:32
  • Nope, the image are refreshed and build up the memory. I use the method you gave me link, it working but still refresh the image cause memory / disk / and CPU go nut. I want reload text only, and ignore update the image – Travgalax Nov 28 '21 at 20:58
  • Widgets are like screenshots. Each timeline entry is a screen shot. `Text` with `style` is the closest thing to what you are asking. – lorem ipsum Nov 28 '21 at 21:11
  • Widgets also have budgets that can increase or decrease depending on how many times the users looks at your widget. https://developer.apple.com/documentation/widgetkit/keeping-a-widget-up-to-date – lorem ipsum Nov 28 '21 at 21:14

0 Answers0