Splitup of Form Inline Navigation Bar Title - swiftui

I created an entry form as an independent project with the navigation bar working great. I then copied the view to my overall project. In the process my inline navigation bar title split up leaving the Back button where it should be and creating a large space before displaying the title and the Save button (See photo below). There is a similar question at How to remove the default Navigation Bar space in SwiftUI NavigiationView but they are trying to remove the space in addition to the title from a List whereas I'm working with a form. Another question with large navigation bar space was due to a duplicate navigation bar title (Removing large amounts of whitespace in a SwiftUI subview). That is not the case here. Any ideas?
here is some of the code:
`struct entryView: View {
#EnvironmentObject var userData: UserData
#Environment(\.managedObjectContext) var viewContext // core data
. . .
var body: some View {
NavigationView {
Form {
// get entry date and time
Section {
HStack {
Image("iconCalendar24")
Spacer()
DatePicker("", selection: $entryDT, in: ...Date())
.datePickerStyle(CompactDatePickerStyle())
}
}
. . .
// select entry category
Section {
HStack {
Text("Select Category")
.font(Font.subheadline.bold())
Picker(selection: $entryCat, label: Text("")) {
ForEach(0 ..< self.categories.count) {
Text(self.categories[$0]).tag($0)
}
}
}
}
.navigationBarTitle(Text("Expense Entry"), displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.saveButton() // button pressed
}) {
Text ("Save")
}.disabled(moneyS.isEmpty)
)
}
}
}
}
Simulator view of the Expense Entry screen

It looks like you already have one NavigationView somewhere in parent view, so you don't need another one here.
Try to remove
struct entryView: View {
#EnvironmentObject var userData: UserData
#Environment(\.managedObjectContext) var viewContext // core data
. . .
var body: some View {
//NavigationView { // << this one !!

I texted out the whole view then slowly brought pieces in one at a time. It turns out the navigation bar return to inline format when I removed NavigationView { } from the view.
It is strange that the navigation bar displayed correctly in the standalone project but then failed when brought into the main project with other files / views.

Related

swiftui how to find out if a sheet is currently being presented

I have an app with many nested views, some which show a sheet based on a user action.
But I also have a sheet that I'd like to present on the main view based on a timer (ie, not a user action). But you can't have 2 sheets at the same time, so I'd like to check "something" to see if a sheet is already up, and not present the one from the timer.
I'd like to do this in a general way, and not check every place in the code where a sheet might be presented.
Any suggestions?
Ideally there'd be something in the core framework that could be queried to answer the question "Is there a sheet being shown?", but as a commenter pointed out, that is fraught with peril.
So I just decided to leave it alone, that the "default" behavior is fine (ie, it'll defer presenting the sheet until any other sheet is dismissed). In my case this is preferred to any other gyrations.
EDIT:
Eek! I just found out that if the sheet from the timer is popped up while an Alert is showing...it ruins the app. Once you dismiss the alert, any attempt to bring up any sheet anywhere fails. It's as if things got out of sync somewhere. I believe this is similar to:
Lingering popover causes a problem with alerts
If you have alerts in your app, you don't really want to do this.
Here is how you can handle the sheets - the example below is fully functioning, just pass the view model to the environment before calling TabsView() in the App.
Create an Identifiable object that will handle all the sheets in the program:
// This struct can manage all sheets
struct CustomSheet: Identifiable {
let id = UUID()
let screen: TypeOfSheet
// All sheets should fit here
#ViewBuilder
var content: some View {
switch screen {
case .type1:
SheetType1()
case .type2(let text):
SheetType2(text: text)
default:
EmptyView()
}
}
// All types of sheets should fit here
enum TypeOfSheet {
case type1
case type2(text: String)
case none
}
}
Create one optional #Published var and one function in the view model; the var will tell the program what sheet is open:
// Code to be included in the view model, so it can
// handle AND track all the sheets
class MyViewModel: ObservableObject {
// This is THE variable that will tell the code whether a sheet is open
// (and also which one, if necessary)
#Published var sheetView: CustomSheet?
func showSheet(_ sheet: CustomSheet.TypeOfSheet) {
// Dismiss any sheet that is already open
sheetView = nil
switch sheet {
case .none:
break
default:
sheetView = CustomSheet(screen: sheet)
}
}
}
Usage:
open the sheets by calling the function viewModel.showSheet(...)
use .sheet(item:) to observe the type of sheet to open
use viewModel.sheet.screen to know what sheet is open
sheets can also be dismissed using viewModel.showSheet(.none)
// Example: how to use the view model to present and track sheets
struct TabsView: View {
#EnvironmentObject var viewModel: MyViewModel
var body: some View {
TabView {
VStack {
Text("First tab. Sheet is \(String(describing: viewModel.sheetView?.screen ?? .none))")
.padding()
Button("Open sheet type 1") {
// Show a sheet of the first type
viewModel.showSheet(.type1)
}
}
.tabItem {Label("Tab 1", systemImage: "house")}
VStack {
Text("Second tab. Sheet is \(viewModel.sheetView == nil ? "Hidden" : "Shown")")
.padding()
Button("Open sheet type 2") {
// Show a sheet of the second type
viewModel.showSheet(.type2(text: "parameter"))
}
}
.tabItem {Label("Tab 2", systemImage: "plus")}
}
// Open a sheet - the one selected in the view model
.sheet(item: $viewModel.sheetView) { sheet in
sheet.content
.environmentObject(viewModel)
}
}
}
The following code completes the minimal reproducible example:
// Just some sample views for the sheets
struct SheetType1: View {
#EnvironmentObject var viewModel: MyViewModel
var body: some View {
Text("Takes no parameters. Sheet is \(viewModel.sheetView == nil ? "Hidden" : "Shown")")
}
}
struct SheetType2: View {
#EnvironmentObject var viewModel: MyViewModel
let text: String
var body: some View {
Text("Takes a string: \(text). Sheet is \(String(describing: viewModel.sheetView?.screen ?? .none))")
}
}
#main
struct MyApp: App {
let viewModel = MyViewModel()
var body: some Scene {
WindowGroup {
TabsView()
.environmentObject(viewModel)
}
}
}

Two NavigationBar Items appears twice in the second current view

I have a current SwiftUI View with a Navigation Bar item . I created a Back Button on this current view so that the title is not inherited from the previous view (NEWSview) with list of news websites to navigate to the current view and clicking on the back button returns me to the previous view. This is sample code of the previous view screen how i did:
struct ContentView: View {
#ObservedObject var networkManager = NetworkManager()
var body: some View {
NavigationView {
List (networkManager.posts) { post in
NavigationLink (destination: DetailView(url: post.url) ) {
HStack {
Text(String(post.points))
Text(post.title)
}
}
}
.navigationBarTitle("News")
}
.onAppear {
DispatchQueue.main.async {
self.networkManager.fetchData()
}
}
}
}
This is the current screen view that is having the two buttons:
struct DetailView: View {
let url : String?
#Environment(\.presentationMode) private var mode
var body: some View {
WebView(urlString: url)
.navigationBarItems(leading:
Button(action: {
self.mode.wrappedValue.dismiss()
}, label: {
HStack(spacing: 10) {
Image(systemName: "chevron.left")
.resizable()
.frame(width: 10.0, height: 19.0)
.font(.headline)
Text("Back")
}
})
)
}
}
Now the issue is : The current screen NavBar has two buttons like this : one with news title and the back button (Picture attached) . I want only to remain with the Back Button on the current view that on click will navigate back to the previous root view . I thought what i did is right but missing something . If remove the entire code with the button in the current view , i will only see the "NEWS" navigation that on click will return me to the News View but i want a back title that should not be inherited from the news view .

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.

Scroll up to see TextField when the keyboard appears in SwiftUI

In my use case, I have to put a TextField below the available items in a List and by using that TextField, we can add items to the List.
Initially, there're no list items (items array is empty)
Here's a minimal, reproducible example
import SwiftUI
struct ContentView: View {
#State var itemName = ""
#State var items = [String]()
var body: some View {
NavigationView {
List {
ForEach(self.items, id: \.self) {
Text($0)
}
VStack {
TextField("Item Name", text: $itemName)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.items.append(self.itemName)
self.itemName = ""
}) {
Text("Add Item")
}
}
}
.navigationBarTitle(Text("Title"))
}
}
}
We can add a new item to the list by typing something in the TextField and clicking "Add Item" Button , Every item that we add using TextField appears above the TextField in the List. So the TextField goes down in the List (Just like Apple’s Reminders app).
If the app has many items (more than 7 items), the keyboard covers the TextField when the keyboard appears and we can’t see the TextField.
Check this screenshot:
What I want to know is how to automatically scroll the List (move the view up) to see the TextField when keyboard appears (like in Apple's Reminders app).
I had a similar problem in my recent project, the easiest way for me to solve it was to wrap UITextField in SwiftUI and from my custom wrapper reach to the parent scroll view and tell it to scroll when the keyboard appears. I tried my approach on your project and it seems to work.
If you take my code for the wrapper and other files from this GitHub folder: https://github.com/LostMoa/SwiftUI-Code-Examples/tree/master/ScrollTextFieldIntoVisibleRange and then replace the SwiftUI TextField with my custom view (TextFieldWithKeyboardObserver) then it should scroll.
import SwiftUI
struct ContentView: View {
#State var itemName = ""
#State var items = [String]()
var body: some View {
NavigationView {
List {
ForEach(self.items, id: \.self) {
Text($0)
}
VStack {
TextFieldWithKeyboardObserver(text: $itemName, placeholder: "Item Name")
Button(action: {
self.items.append(self.itemName)
self.itemName = ""
}) {
Text("Add Item")
}
}
}
.navigationBarTitle(Text("Title"))
}
}
}
I recently wrote an article explaining this solution: https://lostmoa.com/blog/ScrollTextFieldIntoVisibleRange/

Present a View Non-Modally

I am creating a sign in page for my app and would like to present the home screen in a way that the user can not go back. In Swift UI how do I present it so the new view does not present in a card like style? I know this presenting style is now default for iOS 13.
This is what I already have.
import SwiftUI
struct Test : View {
var body: some View {
PresentationButton(Text("Click to show"), destination: Extra() )
}
}
I would like the view to present full screen.
Use a NavigationView with a NavigationButton and hide the destination view's navigation bar's back button.
For example:
struct ContentView : View {
let destinationView = Text("Destination")
.navigationBarItem(title: Text("Destination View"), titleDisplayMode: .automatic, hidesBackButton: true)
var body: some View {
NavigationView {
NavigationButton(destination: destinationView) {
Text("Tap Here")
}
}
}
}
You can also disable the destination view's navigation bar altogether by doing let destinationView = Text("Destination").navigationBarHidden(true).