Reusing a expensive view in SwiftUI

I have a expensive GIF animated view that I use as a activity indicator and it needs to be used in a lot of places in the UI, wherever the user encounters a API call.

In the UIKit world, I would warm up a UIView with the animated gif, and create a new UIWindow, keep a reference to it and add it as a root view and make the window visible and hidden when required. Easy.

However I have a hard time figuring out how I could achieve the same in SwiftUI. Or maybe I’m going about this all wrong. Help appreciated!

Not that it’d help, but what I had in mind for the loading indicator, which I would call via .modifier(LoadingIndicator(...)) wherever required.

struct LoadingIndicator<ProgressViewType: View>: ViewModifier {
    var isShowing: Bool
    @ViewBuilder
    var progressView: () -> ProgressViewType
    func body(content: Content) -> some View {
        ZStack {
            content
            if isShowing {
                loadingView
            }
        }
    }
    
    private var loadingView: some View {
        GeometryReader { proxyReader in
            ZStack {
                Color.white.opacity(0.1)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                
                progressView()
            }
        }
        .ignoresSafeArea()
        //.transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2)))
    }
}

Edit:

Ok, this is what I have so far. To clarify the expensive bit, the loading indicator that is being used is a GIF. I ran and profiled my app a couple of times and the memory usage shoots up.

Every time I profiled when the loading indicator is displayed, the CPU usage reads 20% for a moment, while hovering around 2% otherwise. I’m no a expert, but doesn’t frequently allocating and deallocating drain battery life?

If this is a non-issue, I am happy to leave it as is, since this works as expected.

I have trimmed down and added a reproducible example:

This requires the SDWebImageSwiftUI package. And any 10MB GIF.

import Foundation
import SwiftUI
import SDWebImageSwiftUI

extension View {
    
    @ViewBuilder
    func `if`(_ condition: Bool, transform: (Self) -> some View) -> some View {
        if condition {
            transform(self)
        } else {
            self
        }
    }
}

struct LoadingIndicator<ProgressViewType: View>: ViewModifier {
    var isShowing: Bool
    @ViewBuilder
    var progressView: () -> ProgressViewType
    func body(content: Content) -> some View {
        ZStack {
            content
            if isShowing {
                loadingView
            }
        }
    }
    
    private var loadingView: some View {
        GeometryReader { proxyReader in
            ZStack {
                Color.white.opacity(0.1)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                
                progressView()
            }
        }
        .ignoresSafeArea()
        .transition(AnyTransition.opacity.animation(.easeInOut(duration: 0.2)))
    }
    
    fileprivate static var defaultProgressView: some View {
        ProgressView()
            .progressViewStyle(
                CircularProgressViewStyle(tint: .white)
            )
            .scaleEffect(x: 2, y: 2, anchor: .center)
            .background(
                RoundedRectangle(cornerRadius: 16)
                    .foregroundColor(Color.black.opacity(0.7))
                    .frame(width: 80, height: 80)
            )
    }
}

extension View {
    func loadingIndicator<ProgressView: View>(_ isShowing: Bool, @ViewBuilder progressView: @escaping () -> ProgressView) -> some View {
        
        self.if(isShowing) { $0.blur(radius: 10) }
            .modifier(LoadingIndicator(isShowing: isShowing, progressView: progressView))
    }
    
    func tpActivityIndicator(_ isShowing: Bool) -> some View {
        
        loadingIndicator(isShowing) {
            VStack {
                AnimatedImage(name: "loading_indicator.gif")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 130, height: 150)
                Text("Loading...")
                    .font(.body)
                    .foregroundStyle(Color.white)
            }
        }
    }
}

#Preview {
    VStack {
        Text("Lorem Ipsum")
    }
    .padding()
    .background { Color.yellow }
    .clipShape(RoundedRectangle(cornerRadius: 5))
    .tpActivityIndicator(true)
}

7

Coming from UIKit with reference-based views, the value-based system of SwiftUI can be a little unfamiliar at first.

The most important thing to understand compared to UIKit is that in SwiftUI views should be as lightweight as possible and they are (as mentioned) value types. This also indirectly answers your question about how to make views “reusable” in SwiftUI. The way you use the term, the answer would be “not at all”, because value types are copied and not referenced for reuse.

SwiftUI works in such a way that views are constantly recreated and only their state is transferred to a new instance if required.
I.e. the creation of a SwiftUI view should never be sth. expensive. In principle, the same applies to the layout process of SwiftUI views.

Your actual problem is that you want to optimize the use of an expensive resource that could affect the performance of your app or device.

In your case, according to your description, this is a very large local image (10MB GIF) that needs to be loaded into memory, decompressed and rendered.

As is often the case with this type of problem, you have to choose between different strategies. You can try to load these resources into memory in advance (maybe even in a decompressed state), but this will increase the memory footprint of your app.
Or you can load the resource only when needed, in which case the short-term CPU load increases which may affect your UI.

You have to decide for yourself which strategy is best for your app, as this depends heavily on your specific app and use case.
The tools Instruments provides help you to identify bottlenecks. There are many WWDC videos and blog posts on the web regarding this topic.

For images, you could typically load their data into memory in advance if the image needs to be constantly loaded and displayed in a lot of areas in the app. But then you should always make sure that bitmap-based images do not have to be transformed for display.

You should also ask yourself whether it absolutely has to be a 10MB GIF or whether you can use another more suitable method for the animation display.
For example, by using something like Lottie or another vector-based approach.

If you absolutely want to rely on SDWebImage, you need to take a look at what is offered in the framework in terms of pre-loading or options for retrieving images from a cache.

I.e. SDImageCache could be what you’re looking for.

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật