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.
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
}
}
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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With