Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Keyboard Calls OnAppear of Other Views in TabBar SwiftUI 2.0

I am using UITabBarController in SwiftUI 2.0 and Xcode 12 but seems like Keyboard cases some unexpected behavior. As you can see from the below GIF, OnAppear of the other 2 tab's view called when the keyboard appears in the first tab. That is causing the issue as I have an API call written on appear.

Also, is there any way I can turn off the default view offset behavior of Xcode 12.

enter image description here

Here is my code of Content View.

struct ContentView: View {
    @State private var index:Int = 0
    var menuItems:[String] = ["Item 1", "Item 2", "Item 3"]
    var body: some View {
        NavigationView(content: {
            ZStack{
                MyTabView(selectedIndex: self.$index)
                    .view(item: self.item1) {
                        NewView(title: "Hello1").navigationBarTitle("")
                            .navigationBarHidden(true)
                    }
                    .view(item: self.item2) {
                        NewView(title: "Hello2").navigationBarTitle("")
                            .navigationBarHidden(true)
                    }
                    .view(item: self.item3) {
                        NewView(title: "Hello3").navigationBarTitle("")
                            .navigationBarHidden(true)
                    }
            }.navigationBarHidden(true)
            .navigationBarTitle("")
        })
    }
    
    var item1:MyTabItem {
        
        var item = MyTabItem()
        item.imageName = "pencil.circle"
        item.selectedImageName = "pencil.circle.fill"
        return item
    }
    
    var item2:MyTabItem {
        var item = MyTabItem()
        item.imageName = "pencil.circle"
        item.selectedImageName = "pencil.circle.fill"
        return item
    }
    
    var item3:MyTabItem {
        var item = MyTabItem()
        item.imageName = "pencil.circle"
        item.selectedImageName = "pencil.circle.fill"
        return item
    }
}


struct NewView:View {
    @State var text:String = ""
    var title:String
    var body: some View {
        VStack {
            Spacer()
            Text("Hello")
            TextField(title, text: self.$text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
            
        }.padding()
        .onAppear {
            debugPrint("OnApper \(self.title)")
        }
    }
}

and here is the code for CustomTabView.

class MyTabViewViewModel:ObservableObject {
    var controllers: [UIViewController] = []
    var tabItems:[MyTabItem] = []
}

struct MyTabItem {
    var imageName:String = ""
    var selectedImageName:String = ""
    var hasDarkModeSupport:Bool = true
    var image:UIImage?
    var selectedImage:UIImage?
}

struct MyTabView: UIViewControllerRepresentable {
    
    var viewModel:MyTabViewViewModel = MyTabViewViewModel()
    
    @Binding var selectedIndex: Int
    
    func makeUIViewController(context: Context) -> UITabBarController {
        let tabBarController = UITabBarController()
        tabBarController.viewControllers = self.viewModel.controllers
        tabBarController.delegate = context.coordinator
        tabBarController.selectedIndex = 0
        
        let appearance = tabBarController.tabBar.standardAppearance
        appearance.shadowImage = nil
        appearance.shadowColor = nil
        appearance.backgroundEffect = nil
        tabBarController.tabBar.standardAppearance = appearance
        
        tabBarController.tabBar.shadowImage = UIImage()
        tabBarController.tabBar.backgroundImage = UIImage()
        tabBarController.tabBar.layer.shadowPath = UIBezierPath(rect: tabBarController.tabBar.bounds).cgPath
        tabBarController.tabBar.layer.shadowOffset = CGSize.init(width: 0, height: -3)
        tabBarController.tabBar.layer.shadowRadius = 5
        tabBarController.tabBar.layer.shadowColor = UIColor.black.cgColor
        tabBarController.tabBar.layer.shadowOpacity = 0.25
        tabBarController.tabBar.backgroundColor = UIColor.white
        tabBarController.tabBar.barTintColor = UIColor.white
        
        self.updateTabItems(forTabBarController: tabBarController)
        
        return tabBarController
    }
    
    func updateUIViewController(_ tabBarController: UITabBarController, context: Context) {
        tabBarController.selectedIndex = selectedIndex
        self.updateTabItems(forTabBarController: tabBarController)
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func updateTabItems(forTabBarController tabBarController:UITabBarController) {
        let isDarkModeEnable:Bool = tabBarController.traitCollection.userInterfaceStyle == .dark
        for (index, tabItem) in self.viewModel.tabItems.enumerated() {
            
            tabBarController.tabBar.items?[index].title = ""
            
            if let image = tabItem.image {
                tabBarController.tabBar.items?[index].image = image
                if let selectedImage = tabItem.selectedImage {
                    tabBarController.tabBar.items?[index].selectedImage = selectedImage
                }
            } else {
                if tabItem.hasDarkModeSupport && isDarkModeEnable {
                    if let image = UIImage.init(systemName: "\(tabItem.imageName)-dark") {
                        tabBarController.tabBar.items?[index].image = image
                    } else if let image = UIImage.init(systemName: tabItem.imageName) {
                        tabBarController.tabBar.items?[index].image = image
                    }
                    if let selectedImage = UIImage.init(systemName: "\(tabItem.selectedImageName)-dark") {
                        tabBarController.tabBar.items?[index].selectedImage = selectedImage
                    } else if let selectedImage = UIImage.init(systemName: tabItem.selectedImageName) {
                        tabBarController.tabBar.items?[index].selectedImage = selectedImage
                    }
                } else {
                    if let image = UIImage.init(systemName: tabItem.imageName) {
                        tabBarController.tabBar.items?[index].image = image
                    }
                    if let selectedImage = UIImage.init(systemName: tabItem.selectedImageName) {
                        tabBarController.tabBar.items?[index].selectedImage = selectedImage
                    }
                }
            }
        }
    }
    
    class Coordinator: NSObject, UITabBarControllerDelegate {
        var parent: MyTabView
        
        init(_ tabBarController: MyTabView) {
            self.parent = tabBarController
        }
        
        func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
            parent.selectedIndex = tabBarController.selectedIndex
        }
    }
    
    func view<HostedView:View>(item:MyTabItem, @ViewBuilder sheet: @escaping () -> HostedView) -> MyTabView {
        self.viewModel.controllers.append(UIHostingController.init(rootView: sheet()))
        self.viewModel.tabItems.append(item)
        return self
    }
}
like image 856
Malav Soni Avatar asked Sep 18 '20 12:09

Malav Soni


People also ask

What is tabview in SwiftUI?

TabBar on the bottom of the screen is one of the most important building blocks for modern iOS applications. Apple themselves is using it frequently in their apps. With SwiftUI, this element now has the new name TabView. It allows us to add the tab view and control the currently selected tab programmatically.

What is tabview in UIKit?

It allows us to add the tab view and control the currently selected tab programmatically. The hidden feature of the TabView is that we can use it to show the multiple tabs with page indicators, and those can be controlled by scrolling between them. In UIKit, it was UIPageViewController.

How to create scrolling pages with the tabview in SwiftUI?

To create scrolling pages with the TabView in SwiftUI, we need to call the view modifier tabViewStyle and pass an instance of PageTabViewStyle style. If we want to hide the page indicator, we specify the indexDisplayMode parameter for the PageTabViewStyle instance and set it to .never.

What is Tabbar in iOS?

The hidden secrets of TabView in iOS with SwiftUI Apr 22, 2021 TabBar is a vital component of iOS and has been from iOS 2.0. This element appears at the bottom of the iOS and iPadOS devices and allows our app users to switch between different views or functions quickly.


1 Answers

Having the same issue myself

"Hackish" workaround is to wrap the NewView.body in a List:

    @State var text:String = ""
    var title:String
    var body: some View {
        List {
            VStack {
                Spacer()
                Text("Hello")
                TextField(title, text: self.$text)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                
            }.padding()
            .onAppear {
                debugPrint("OnApper \(self.title)")
            }
        }
    }
}

Could also work to use a LazyVStack, but haven't gotten to test it as my project targets 13.x

Same issue here OnAppear calls unexpectedly when Keyboard Appears in SwiftUI

like image 109
sgulseth Avatar answered Oct 18 '22 20:10

sgulseth