SwiftUI TabView More tab crashes first press - swiftui

var body: some View {
TabView {
ScheduleView().tabItem {
Image(systemName: "calendar")
Text("Schedule")
}.tag(tbScheduleTag)
DutyBookView().tabItem {
Image(systemName: "books.vertical")
Text(dutyBookViewTabText)
}.tag(tbDutyBookTag)
TimetableView().tabItem {
Image(systemName: "calendar.badge.clock")
Text("Timetable")
}.tag(tbTimetableTag)
**... plus 7 other Tabs removed for post readability**
}
}
My TabView creates 10 Tabs so I automatically get the "More" TabItem. After App launches and I press that More tab before anything else the Tab crashes back to the Initial tab. If I tap any other tab first or the More tab again it loads fine. See attached for demo. The first time selected it crashes back the second time it works.
Anyone suggestions please?

I added #State variable and set it to selection attribute of TabView. To make this work I replaced your tags with new enum values.
struct ContentView: View {
enum Tab {
case schedule, dutyBook, timetable, locateTrain, settings
case tfLRestricted, subscription, serviceStatus, info, storedDuties
}
#State var tab: Tab = .schedule
var body: some View {
TabView(selection: self.$tab) { //this is the solution
ScheduleView().tabItem {
Image(systemName: "calendar")
Text("Schedule")
}.tag(Tab.schedule)
DutyBookView().tabItem {
Image(systemName: "books.vertical")
Text(dutyBookViewTabText)
}.tag(Tab.dutyBook)
// ...
}
}
}

Related

SwiftUI - NavigationLink and NavigationStack is not working with a button for iOS 16

I am making an app where I need to navigate to the home page when the user clicks on the Login button and when the Login button is clicked, the navigation link code is not working and shows a warning as Result of 'NavigationLink<Label, Destination>' initializer is unused. FYI, please refer to the attached screenshot and the below code:
import SwiftUI
struct LoginView: View {
var nextButton: some View {
HStack {
Button("Next") {
NavigationLink {
HomeView(user: user)
} label: {
Text("Test")
}
}
.buttonStyle(PlainButtonStyle())
.font(.system(size: 24))
}
}
var body: some View {
NavigationStack {
nextButton
}
}
}
There are two problems here:
A NavigationLink is an alternative to a Button and can be used on its own for navigation. You can use a button to navigate programatically, but for this simple case a link works. Like Buttons, you can change the appearance of NavigationLinks if needed.
NavigationStack is a more powerful replacement for NavigationView. You should not mix them both. As the HomeView is your root view, the single NavigationStack for your app should be there (not in the LoginView).
struct HomeView: View {
var body: some View {
NavigationStack {
VStack {
NavigationLink {
LoginView()
} label: {
Text("Login")
}
}
}
}
}
The warning is because you are creating a NavigationLink in the button action code block, and returned NavigationLink is not assigned or used.

Combining Navigation Bar, TabView and searchable causes the NavigationBar UI issue with lists

Combining Navigation Bar, TabView and searchable causes the NavigationBar and Search InputField to stay stationary when scrolling up on the second selected tab.
If I run the code below and first click on the Bookmark tab and scroll the list up, I get the desired results as shown in Figure 1.
If I immediately click the Home tab after the Bookmark tab and scroll the list up, I get an undesirable effect of the list displaying underneath the navigation header as shown in Figure 2.
The order that you click on the tabs produces different effects, and the position you last left the list before going to the next tab also has some strange influence on the behavior.
I need to use the TabView because it "remembers" the position of the list when you move from tab to tab. Creating my own tab control causes the list to reset everytime its displayed and I understand why. We also need to wrap the TabView under the NavigationView because our application subviews need to display their own tabs.
My questions, what am I doing wrong that is causing these inconsistencies in the navigation header. I have tried putting each list in it's own Stack but no joy, same issue keeps happening.
Any assistance would be greatly appreciated, we are currently blocked on our navigation design because of this anomaly. Hoping it's out fault so we can correct it.
----> the complete code <-------------------------------------------------------
struct ContentView: View {
#State var selectedTab: String = "Home"
#State var searchText: String = ""
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
ListView(itemTitle: "Home List", itemCount: 50)
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}.tag("Home")
ListView(itemTitle: "Bookmark List", itemCount: 20)
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Bookmark")
}.tag("Bookmark")
Text("Profile Tab")
.tabItem {
Image(systemName: "person.crop.circle")
Text("Profile")
}.tag("Profile")
}
.navigationTitle(selectedTab)
}
.searchable(text: $searchText)
.onSubmit(of: .search) {
// Do something
}
}
}
struct ListView: View {
var itemTitle: String
var itemCount: Int
var body: some View {
List(){
ForEach(1...itemCount,id: \.self){ i in
NavigationLink(destination: ListViewDetailView("\(itemTitle) \(i)")) {
VStack(alignment: .leading){
Text("\(itemTitle) \(i)").padding()
}
}
}
}
}
}
struct ListViewDetailView: View {
var text:String
init(_ text: String){
self.text = text
}
var body: some View {
Text(text).navigationTitle(Text("\(text) Detail"))
}
}

Toolbar does not appear properly SwiftUI

In my GeneralView I have a NavigationView And a Tab View.
Inside each tabItem I navigate with some ZStack (using zIndex, hiding and showing items)
Randomly leading and trailing items are not shown properly and can't be clicked.
See below, on top of screen back button is not full. But I select same button to go on the "Coureur1View"
Info : I do not have any other problem with this navigation.
In My generalView :
.toolbar {
ToolbarItemGroup(placement: .principal) {
TitleBarView().environmentObject(objCourse)
}}
.navigationBarItems(leading: TitleBarLeadingView(),
trailing: TitleBarTrailingView())
I don't have problem with TitleBarView (principale) but with leading and trailing
In my TitleBarLeadingView :
struct TitleBarLeadingView: View {
#EnvironmentObject var objGroupe : GroupeActuel
#EnvironmentObject var objCourse : CourseActuelle
#EnvironmentObject var zindex : Zindex
var body: some View {
HStack {
if zindex.selectedTab > 0 {
if zindex.detailCoureurVisible {
Button{
zindex.detailCoureurVisible = false
} label : {
Image(systemName: "chevron.backward")
Text("Back")
}.foregroundColor(.orange)
}else{
EmptyView()
}else{
EmptyView()
}
}
}
Provided snapshots are not testable, so just idea - try to recreate navigation bar items forcefully. It can be on some known changed value (I see titled changed on gif), but also can be just by UUID():
.navigationBarItems(leading: TitleBarLeadingView().id(UUID()),
trailing: TitleBarTrailingView().id(UUID()))
Note: make .id(param) is preferable because by UUID it will be recreated by each refresh.

SwiftUI: Change navigation bar title in the more tab?

I am begining to get my haead around swiftUI. So I have a simple tabView inside a Navigation View as below.
import SwiftUI
struct BasicView: View {
var climbList: [ClimbDetail]
var body: some View {
NavigationView{
VStack{
Text("").navigationBarTitle("Climbers Log", displayMode:.inline)
TabView {
Text("Search").tabItem{
Image(systemName:"magnifyingglass")
Text("Search")
}
Text("Stats").tabItem{
Image(systemName:"list.dash")
Text("Stats")
}
ClimbList(climbs: climbList).tabItem{
Image(systemName: "square")
Text("Climbs")
}
Text("Log").tabItem{
Image(systemName:"square.and.pencil")
Text("Log")
}
Text("Profile").tabItem{
Image(systemName:"person.circle")
Text("Profile")
}
Text("Settings").tabItem{
Image(systemName:"gear")
Text("Settings")
}
Text("Add Climb").tabItem{
Image(systemName:"plus")
Text("Add Climb")
}
}
}
}
}
}
Genrally it works as expected, however as I have 7 tabs it defults to the 'More' tab for the 5th tab. This is fine and good for the user.
However my issue is when you click the 'More' tab you get the a title bar and edit button with 'More' as the title. Which appears below the title bar I have set above.
So my question is how can I hide my titleBar when the user is on the 'More' tab and only show it inside the other tabs?
First off all, thanks for the question. I didn't know Apple provides More page by default for too long TabBars. I always needed that.
Back to you question, you just need to rearrange things. First off all, make the TabBar top level. Then a NavigationView and here comes your content. Later on you might outsource every view, to an own file.
TabView {
NavigationView
{
VStack
{
Text("Search")
}
.navigationBarTitle("Climbers Log", displayMode:.inline)
}.tabItem {
Image(systemName:"magnifyingglass")
Text("Search")
}
Every view takes it own NavigationView.. and then it works.

How to Hide Keyboard in SwiftUI Form Containing Picker?

I have a SwiftUI Form that contains a Picker, a TextField, and a Text:
struct ContentView: View {
var body: some View {
Form {
Section {
Picker(selection: $selection, label: label) {
// Code to populate picker
}.pickerStyle(SegmentedPickerStyle())
HStack {
TextField(title, text: $text)
Text(text)
}
}
}
}
}
The code above results in the following UI:
I am able to easily select the second item in the picker, as shown below:
Below, you can see that I am able to initiate text entry by tapping on the TextField:
In order to dismiss the keyboard when the Picker value is updated, a Binding was added, which can be seen in the following code block:
Picker(selection: Binding(get: {
// Code to get selected segment
}, set: { (index) in
// Code to set selected segment
self.endEditing()
}), label: label) {
// Code to populate picker
}
The call to self.endEditing() is provided in the following method:
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
The following screenshot displays that selecting a different segment of the Picker dismisses the keyboard:
Up to this point, everything works as expected. However, I would like to dismiss the keyboard when tapping anywhere outside of the TextField since I am unable to figure out how to dismiss the keyboard when dragging the Form's containing scroll view.
I attempted to add the following implementation to dismiss the keyboard when tapping on the Form:
Form {
Section {
// Picker
HStack {
// TextField
// Text
}
}
}.onTapGesture {
self.endEditing()
}
Below, the following two screenshot displays that the TextField is able to become the first responder and display the keyboard. The keyboard is then successfully dismissed when tapping outside of the TextField:
However, the keyboard will not dismiss when attempting to select a different segment of the `Picker. In fact, I cannot select a different segment, even after the keyboard has been dismissed. I presume that a different segment cannot be selected because the tap gesture attached to the form is preventing the selection.
The following screenshot shows the result of attempting to select the second value in the Picker while the keyboard is shown and the tap gesture is implemented:
What can I do to allow selections of the Picker's segments while allowing the keyboard to be dismissed when tapping outside of the TextField?
import SwiftUI
struct ContentView: View {
#State private var tipPercentage = 2
let tipPercentages = [10, 15, 20, 25, 0]
#State var text = ""
#State var isEdited = false
var body: some View {
Form {
Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
HStack {
TextField("Amount", text: $text, onEditingChanged: { isEdited in
self.isEdited = isEdited
}).keyboardType(.numberPad)
}
}
}.gesture(TapGesture().onEnded({
UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
}), including: isEdited ? .all : .none)
}
}
Form's tap gesture (to finish editing by tap anywhere) is enabled only if text field isEdited == true
Once isEdited == false, your picker works as before.
You could place all of your code in an VStack{ code }, add a Spacer() to it and add the onTap to this VStack. This will allow you to dismiss the keyboard by clicking anywhere on the screen.
See code below:
import SwiftUI
struct ContentView: View {
#State private var text: String = "Test"
var body: some View {
VStack {
HStack {
TextField("Hello World", text: $text)
Spacer()
}
Spacer()
}
.background(Color.red)
.onTapGesture {
self.endEditing()
}
}
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Changing the background color of an HStack or VStack to red simplifies figuring out where the user may click to dismiss.
Copy and paste code for a ready to run example.