Calling viewDidAppear intentionally

I am developing SDK and swizzling viewDidAppear. I have a customer who implements a custom TabBar Navigation where VC's are added to the hierarchy on the first load, and then, he changes the opacity to the currently displayed tab so the next time the user sees the tab - viewDidAppear isn't called, so my code isn't called. I'm attaching a sample project which reproduces that. Is there any way to trigger ViewDidAppear intentionally? If yes, what can be the side effect of doing that? Do I have any other alternative in this case?


@main
struct DemoCustomTabViewApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            TabBarRouterView()
        }
    }
}
import UIKit

// MARK: - TabBarItem (unchanged)

enum TabBarItem: Identifiable, CaseIterable {
    case home, search, profile
    
    var id: Self { self }
    
    var title: String {
        switch self {
        case .home: return "Home"
        case .search: return "Search"
        case .profile: return "Profile"
        }
    }
    
    var icon: String {
        switch self {
        case .home: return "house"
        case .search: return "magnifyingglass"
        case .profile: return "person"
        }
    }
}

// MARK: - NavigationControllerView

struct NavigationControllerView: UIViewControllerRepresentable {
    var rootViewController: UIViewController
    
    func makeUIViewController(context: Context) -> UINavigationController {
        let navigationController = UINavigationController(rootViewController: rootViewController)
        return navigationController
    }
    
    func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {}
}

// MARK: - TabBarRouterViewModel

class TabBarRouterViewModel: ObservableObject {
    @Published var currentTab: TabBarItem = .home
    @Published var cachedViews: [TabBarItem: AnyView] = [:]
    
    let tabs: [TabBarItem] = TabBarItem.allCases
    
    func switchTab(to tab: TabBarItem) {
        currentTab = tab
    }
    
    func createView(for tab: TabBarItem) -> AnyView {
        if let cachedView = cachedViews[tab] {
            return cachedView
        }
        
        let rootViewController: UIViewController
        switch tab {
        case .home:
            rootViewController = UIHostingController(rootView: Text("Home View"))
        case .search:
            rootViewController = UIHostingController(rootView: Text("Search View"))
        case .profile:
            rootViewController = UIHostingController(rootView: Text("Profile View"))
        }
        
        let navigationView = NavigationControllerView(rootViewController: rootViewController)
        let anyView = AnyView(navigationView)
        cachedViews[tab] = anyView
        return anyView
    }
}

// MARK: - CustomTabBarView (unchanged)

struct CustomTabBarView: View {
    let tabs: [TabBarItem]
    @Binding var selectedTab: TabBarItem
    let onTap: (TabBarItem) -> Void
    
    var body: some View {
        HStack {
            ForEach(tabs) { tab in
                Spacer()
                VStack {
                    Image(systemName: tab.icon)
                        .font(.system(size: 24))
                    Text(tab.title)
                        .font(.caption)
                }
                .foregroundColor(selectedTab == tab ? .blue : .gray)
                .onTapGesture {
                    onTap(tab)
                }
                Spacer()
            }
        }
        .frame(height: 60)
        .background(Color.white)
        .shadow(radius: 2)
    }
}

// MARK: - TabBarRouterView

struct TabBarRouterView: View {
    @StateObject private var viewModel = TabBarRouterViewModel()
    
    var body: some View {
        VStack(spacing: .zero) {
            contentView
            
            CustomTabBarView(
                tabs: viewModel.tabs,
                selectedTab: $viewModel.currentTab,
                onTap: viewModel.switchTab
            )
        }
        .edgesIgnoringSafeArea(.bottom)
    }
    
    private var contentView: some View {
        ZStack {
            ForEach(viewModel.tabs) { tab in
                viewModel.createView(for: tab)
                    .opacity(viewModel.currentTab == tab ? 1.0 : 0.0)
            }
        }
    }
}
Answered by DTS Engineer in 801662022
I am developing SDK and swizzling viewDidAppear.

Don’t do that. Swizzling methods in system framework often leads to mysterious problems like the ones you’ve described here and is most definitely not supported.

Rather than continue down this path, I recommend that you rework your SDK’s interface to avoid the need for swizzling. For example, if you need the developer to inform you of an event, like a view appearing, add an interface to your SDK that they call when that event happens.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I am developing SDK and swizzling viewDidAppear.

Don’t do that. Swizzling methods in system framework often leads to mysterious problems like the ones you’ve described here and is most definitely not supported.

Rather than continue down this path, I recommend that you rework your SDK’s interface to avoid the need for swizzling. For example, if you need the developer to inform you of an event, like a view appearing, add an interface to your SDK that they call when that event happens.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Calling viewDidAppear intentionally
 
 
Q