I'm following this page for how to implement a keyboard accessory in SwiftUI. The done button is showing up and functioning as expected for the TextField I am modifying. However, every other keyboard in the same view also ends up decorated.
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
TextField("", text: .constant(""))
.textFieldStyle(.roundedBorder)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Button("Click me!") {
print("Clicked")
}
}
}
TextField("", text: .constant(""))
.textFieldStyle(.roundedBorder)
}
}
}
Why is the 2nd TextField here also receiving the Toolbar when it is not being modified? Is this a bug, or am I misunderstanding something?
Related
I have been trying to figure out what is causing the space at the top of the screen in my production app, so I made this test app to see if it is a bug or not. The code works as intended on a simulator but when a testing device runs the code it adds extra space. The space goes away after you start scrolling, and does not comeback until the view reloads. I have tried restarting the device and other devices. I took out .navigationTitle and .navigationBarTitleDisplayMode and it did not fix the problem. So far my best guess is that there is some problem with changing the section header in .onAppear(). Changing it to .task() seems to be a workaround for now.
struct DetailView: View {
#State var item: Item
#State private var headerText = "Header"
var body: some View {
List {
Section(header: Text("\(headerText)")) {
Text("Text")
}
HStack {
Text("Red Text")
}.listRowBackground(Color.red)
// Change to .task instead
}.onAppear {
headerText = "Change Header"
}
}
}
Edit: Here is the code for the list view, it is the default new project setup.
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: DetailView(item: item), label: {
Text(item.timestamp!, formatter: itemFormatter)
})
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
In my case this behaviour was caused by .navigationViewStyle(StackNavigationViewStyle()).
Looks like it's a bug with NavigationView in SwiftUI 4 since it wasn't like this before.
If you are on iOS 16 use NavigationStack instead NavigationView. This fixed all my problems and I didn't even need to use the navigationViewStyle anymore.
NavigationView is deprecated in favor of NavigationStack.
I'm pretty new to SwiftUI, trying to teach myself a few things here and there. But there's this one issue that's been eating at me for a while... and I can't figure out why the toolbar doesn't work/show for me.
The sample code is below, but the button doesn't show nor is there an actual bar. I have iOS 15.2, with XCode 13.2 beta.
TextField("placeholder", text: $text)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
HStack {
Button(action: {
hideKeyboard()
}) {
Text("Done")
}
}
}
}
EDIT:
Figured out the reason why... it just wouldn't work in a scroll view for some reason. Anyone know why?
Under iOS 15.2 - Xcode 13.2 (not beta).
struct ContentView: View {
#State var text: String = ""
var body: some View {
NavigationView {
VStack {
Text(text)
TextField("Enter your name", text: $text)
}
.padding()
.navigationTitle("SwiftUI")
.toolbar {
// ToolbarItem(placement: .keyboard) {
// Button("Ok") {
// print("ok")
// }
// }
ToolbarItemGroup(placement: .keyboard) {
HStack {
Button("Press Me") {
print("Pressed")
}
Spacer()
Button(action: {
print("done")
}) { Text("Done") }
}
}
}
}
}
}
Make sure you put everything in a VStack or similar.
Xcode 13.3 (release version), in order for the toolbar to show up, the TextField and the .toolbar need to be inside of a NavigationView.
In my particular case I didn't want a NavigationBar, so I ended up with something like this to make it work:
var body: some View {
NavigationView {
VStack {
TextField("Enter your name", text: $text)
// Additional text fields go here, all text fields will get the toolbar.
}
.navigationTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Dismiss") {
print("Bismiss keyboard...")
}
}
}
}
}
I have a button called "save" that saves the user inputs.
But, I want to make it like, if the user tap on Button "Save", then the screen automatically goes back to the previous view. Can I do that by just adding a code to an action in Button? or do I have to use NavigationLink instead of Button?
Button(action: {
let title = shortcutTitle
currentShortcutTitle = title
UserDefaults.standard.set(title, forKey: "title")
}, label: {
Text("Save")
.padding()
.frame(width: 120, height: 80)
.border(Color.black)
}) //: Button - save
If you're just trying to go back to the previous view and already inside a NavigationView stack, you can use #Environment(\.presentationMode):
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Screen2()) {
Text("Go to screen 2")
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct Screen2 : View {
#Environment(\.presentationMode) var presentationMode //<-- Here
var body: some View {
Button("Dismiss") {
presentationMode.wrappedValue.dismiss() //<-- Here
}
}
}
I am new in swift ui. I want to put image button on the side of the NavigationBar title.
I want to be able to click the user image and navigate to another view. How?
You need to use navigationBarItems for putting image to navigation bar and you should add NavigationLink to that image. For center the title you need to set navigation bar title's displayMode to .inline or you can use new Toolbar api
struct ContentView: View {
var body: some View {
NavigationView {
Text("Welcome to Stack Overflow")
.navigationBarTitle("Header", displayMode: .inline)
.navigationBarItems(leading: NavigationLink(destination: Text("Destination")) {
Image(systemName: "person.crop.circle.fill")
.font(.title)
})
}
}
}
Screenshot
Another way using toolbar item.
I am adding TapGesture on image icon, and keeping it out of navigation link, as the image is not getting circular inside the NavigationLink in ToolbarItemGroup.
By leveraging isActive property of NavigationLink which monitors onTap state we can determine either we want to push our view or not.
import SwiftUI
struct WeatherView: View {
#State var onTap = false
var borderColor: Color = Color("Black")
var addProjectToolbarItem: some ToolbarContent {
ToolbarItemGroup(placement: .navigationBarLeading) {
NavigationLink(destination:Text("Welcome"), isActive: self.$onTap) {
EmptyView()
}
Image("yourImage")
.resizable()
.frame(width: 32, height: 32)
.clipShape(Circle())
.onTapGesture {
onTap.toggle()
}
}
}
var body: some View {
NavigationView {
VStack {
Text("First view")
}
.toolbar{
addProjectToolbarItem
}
.navigationTitle("Header")
.navigationBarTitleDisplayMode(.inline)
}
}
}
I don't know if this is a bug or I am doing something wrong here. I've added a new button on the Navigation bar that would present a new view.
struct MyView: View {
#ObservedObject var viewModel = MyViewModel()
var body: some View {
List(viewModel.data, id: \.name) { data in
NavigationLink(destination: MyDetailView(data: data.name)) {
Text(data.name)
}
}
.listStyle(InsetGroupedListStyle())
.edgesIgnoringSafeArea(.all)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: MyDetailView()) {
Text("New Element")
}
}
}
}
}
This is being tested on the newest iOS 14 beta (beta 6) and Xcode 12 (beta 6). As far as I know a Navigation Link presents fine the new view when on a List but in the toolbar as shown that's not the case. The button on the toolbar it's visible and active but doesn't trigger showing the new view.
I found using an HStack with an empty text as the first element also works, it lets the navigationLink act correctly.
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Text("")
NavigationLink(destination: SettingsView()) {
Image(systemName: "gear")
.font(.title)
}
}
}
NavigationLink should be inside NavigationView. Toolbar is not in NavigationView, put buttons in it.
So assuming you have somewhere in parent
NavigationView {
MyView()
}
here is a solution:
struct MyView: View {
#ObservedObject var viewModel = MyViewModel()
#State private var showNew = false
var body: some View {
List(viewModel.data, id: \.name) { data in
NavigationLink(destination: MyDetailView(data: data.name)) {
Text(data.name)
}
}
.listStyle(InsetGroupedListStyle())
.background(
NavigationLink(destination: MyDetailView(), isActive: $showNew) {
EmptyView()
}
)
.edgesIgnoringSafeArea(.all)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("New Element") {
self.showNew = true
}
}
}
}
}
Using Asperi solution may not work if your navigation link directs to a view with keyboard input.
After navigation link, toolbar in the new view loaded correctly but when providing input with keyboard and dismissing keyboard all toolbar items disappears.
The solution is to place NavigationLink not in View but in navigationBarItems, example:
.navigationBarItems(
leading:
NavigationLink(
destination: Creator(),
isActive: $showCreator,
label: {
Text("")
}))