Memory leak in TabView with PageTabViewStyle

Viewed 641

I'm using a tab view using the UIPageViewController behaviour. So I defined the following model:

class WalktroughModel: Identifiable, ObservableObject {
  let id: UUID = UUID()
  let imageName: String
  let title: String

  init(imageName: String, title: String) {
      self.imageName = imageName
      self.title = title
  }
}

Now I use this swiftUI view as a child view of tab view:

struct WalktroughAsset: View {

  @StateObject var asset: WalktroughModel

  var body: some View {
      Image(asset.imageName)
          .resizable()
          .overlay(Color.black.opacity(0.43))
          .overlay(
              VStack{
                  Spacer()
                  Text(asset.title)
                      .foregroundColor(.white)
                      .font(.custom("OpenSans-regular", size: 22.0))
              }
              .padding(.bottom, 64)
          )
    }
}

In my content view I have the following:

struct ContentView: View {

   var thumbs: [WalktroughModel] = [WalktroughModel(imageName: "splash-1", title: "Concepto 1"), WalktroughModel(imageName: "splash-2", title: "Concepto 2"), WalktroughModel(imageName: "splash-3", title: "Concepto 3")]

   var body: some View {
       ZStack {
           Color.black.overlay(TabView{
               ForEach(thumbs) {
                   image in
                   WalktroughAsset(asset: image)
               }
           }
           .tabViewStyle(PageTabViewStyle())
           .padding([.bottom, .top], 32)
           )
        
       }
       .edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
    
   }
}

Now, when I build and run the memory jumps 80 mb to 160 mb when I swipe to the other view and jumps to 230 mb when I swipe to the third view. What could be happen?

Best Regards

2 Answers

This code fixes a memory leak for me

struct TabViewWrapper<Content: View, Selection: Hashable>: View {
    @Binding var selection: Selection
    @ViewBuilder let content: () -> Content
    
    var body: some View {
        TabView(selection: $selection, content: content)
    }
}

Replace TabView(selection:) to TabViewWrapper(selection:)

TabViewWrapper(selection: $selection) {
    tabContent
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))

This bug is presented when you use custom view with args

Similar issue on iOS 15 (Tested on 15.2)

Memory use for the below SwiftUI app will continuously increase over time. The app is simply showing a TabView in a full screen cover, and has a timer that refreshes a countdown every second. If I display a simple text element instead of the TabView, the memory use will behave properly. I submitted a feedback to Apple.

import SwiftUI

class ContentView_manager : ObservableObject {
    var view_timer: Timer?
    @Published var countdown_txt: String?
    
    func startTimer(){
        if self.view_timer == nil  {
                       self.view_timer = Timer(fireAt: Date(), interval: 1, target: self, selector: #selector(self.timer_action), userInfo: nil, repeats: true)
                       RunLoop.current.add(self.view_timer!, forMode: RunLoop.Mode.common)
        }
    }
    
    func stopTimer(){
        if self.view_timer != nil  {
            self.view_timer?.invalidate()
            self.view_timer = nil
        }
    }
    
    @objc
    func timer_action(){
        //update number of seconds until January 2023
        print("Timer Action")
        self.countdown_txt = String(DateInterval(start: Date(), end: Date(timeIntervalSince1970: 1672527600)).duration)
    }
}

@main
struct debugApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(view_manager:ContentView_manager())
        }
    }
}

struct ContentView : View {
    @ObservedObject var view_manager : ContentView_manager
    @State var show_cover : Bool = false
    var body: some View {
       
        VStack{
            Button(action: {
                self.show_cover = true
            }, label: {Text("Show screen cover with TabView")})
        }
        .fullScreenCover(isPresented: self.$show_cover){ScreenCover(view_manager: view_manager)}
    }
}

struct ScreenCover: View {
    @ObservedObject var view_manager : ContentView_manager
    var body: some View {
        tab(txt:view_manager.countdown_txt)
        .onAppear(perform: {
            view_manager.startTimer()
        })
        .onDisappear(perform: {
            view_manager.stopTimer()
        })
        
    }
}


struct tab: View {
    var txt: String?
    var body: some View {
        TabView{
            Text("First Tab")
                   .tabItem {
                       Image(systemName: "1.square.fill")
                       Text("First")
                   }
            Text("Next year in \(txt ?? "") seconds!")
                   .tabItem {
                       Image(systemName: "2.square.fill")
                       Text("Second")
                   }
               Text("Third tab")
                   .tabItem {
                       Image(systemName: "3.square.fill")
                       Text("Third")
                   }
        }
        .tabViewStyle(PageTabViewStyle())
        
    }
}
Related