How dismiss keyboard in a sheet with inherited toolbar+button - swiftui

A view has a TextField and the keyboard has a "Dismiss Keyboard" button (in the toolbar) to remove focus from the textField and dismiss the keyboard. That works fine but when a sheet appears that also contains a TextField the keyboard inherits the button from the previous view and will not dismiss the sheet's keyboard.
I have tried overwriting with a new ToolbarItemGroup within the sheet but it has no effect. The button references the first view. Is there a way to have each keyboard appearance reference the view within which the relevant TextField is situated?
import SwiftUI
struct ContentView: View {
#FocusState private var focusedField1: Bool
#State private var name1 = ""
#State private var showSheet = false
var body: some View {
VStack {
TextField("Hello", text: $name1)
.focused($focusedField1)
.textFieldStyle(.roundedBorder)
.padding()
Button("Show Sheet") { showSheet = true }
}
.toolbar {
toolBarContent1()
}
.sheet(isPresented: $showSheet) {
MySheet()
}
}
#ToolbarContentBuilder func toolBarContent1() -> some ToolbarContent {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button {
print("11111")
focusedField1 = false
} label: { Text("Dismiss Keyboard") }
}
}
}
struct MySheet: View {
#Environment(\.dismiss) var dismiss
#FocusState private var focusedField2: Bool
#State private var name2 = ""
var body: some View {
VStack {
TextField("Hello", text: $name2)
.focused($focusedField2)
.textFieldStyle(.roundedBorder)
.padding()
Button("Dismiss Sheet") {
focusedField2 = false
dismiss()
}
.toolbar {
toolBarContent2()
}
}
}
#ToolbarContentBuilder func toolBarContent2() -> some ToolbarContent {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button {
print("22222")
focusedField2 = false
} label: { Text("Dismiss Keyboard") }
}
}
}

Your code is fine. To make this work, just put your sheet inside a NavigationView{}. Code is below the image:
.sheet(isPresented: $showSheet) {
NavigationView {
MySheet()
}
}

Related

TextField #State property value = ""

I'm studying SwiftUI and I'm having a hard time dealing with TextField.
When I do onCommit, I tried to put "" in the #State Property value, but there was no response.
What I'm trying to do is click the'retrun' key in TextField and I want to show you the gap where the text created disappeared.
struct TodoTextFieldView: View {
#EnvironmentObject var listViewModel: ListViewModel
#State var textFieldText = ""
var title: String = "here typing line"
var body: some View {
VStack{
HStack(spacing: 5){
Text("🔥")
.font(.title)
TextField("\(title)",
text: $textFieldText,
onCommit:{ addItem()
//
})
.disableAutocorrection(true)
.underline()
.font(.headline)
}.padding(.leading, 40)
}.padding(16)
}
func addItem() {
listViewModel.addItem(title: textFieldText)
textFieldText = "" // This is where I want to do it.
print(textFieldText)
}
This is a parent view.
struct ContentView: View {
#EnvironmentObject var listViewModel: ListViewModel
var body: some View {
VStack {
TodayView()
Spacer()
TodoTextFieldView()
Spacer()
/// List view
List{
ForEach(listViewModel.items) { item in
ListView(item: item)
.onTapGesture {
withAnimation(.linear){
listViewModel.updateItem(item: item)
}
}
}
.onDelete(perform: listViewModel.deleteItem) // Delete
.onMove(perform: listViewModel.moveItem) // Edit
}.listStyle(PlainListStyle())
}.navigationBarItems(trailing: EditButton()) // Edit Button
.padding()
}
}
#State var textFieldText = ""
The value was directly added to the state property. But there is no reaction.
textFieldText = ""
Emptying the binding property of TextField inside the Task block works for me.
func addItem() {
listViewModel.addItem(title: textFieldText)
Task {
textFieldText = ""
}
}

SwiftUI - Navigation title background becomes transparent when VStack is visible

I'm running into an issue with the navigation title header in SwiftUI. It's a combination of a couple of things, as far as I can tell...
The main problem is that I'm trying to change the default background color of a view that contains a list. But when I use the tag .background(), the navigation title background becomes transparent. This only happens when there is a VStack on the view.
I have a simplify example code that shows the problem I'm facing:
ContentView:
import SwiftUI
struct ContentView: View {
#State var showButton: Bool
var body: some View {
VStack {
NavigationStack {
NavigationLink(
destination: SecondView(showButton: showButton),
label: {
Text("Take me to second view")
})
Toggle("VStack Visibile", isOn: $showButton)
.padding()
}
}
}
}
SecondView:
import SwiftUI
struct SecondView: View {
#State private var isButtonVisible: Bool = false
#State var showButton: Bool = true
var body: some View {
VStack {
List(0..<10) { _ in
Text("Hello World")
}
if showButton {
button
}
}
.navigationTitle("This is a title")
.background(Color(.systemCyan))
}
var button: some View {
Text("Something")
}
}
Please see below the resulting problem:
Issues / Suggestions:
ContentView
Have the NavigationStack outside the VStack
SecondView
Don't embed List inside a VStack
List is special and has special characteristics
Don't initialise #State property from outside, pass a binding instead
Code:
ContentView:
struct ContentView: View {
#State var showButton = true
var body: some View {
NavigationStack {
VStack {
NavigationLink(
destination: SecondView(showButton: $showButton),
label: {
Text("Take me to second view")
})
Toggle("VStack Visibile", isOn: $showButton)
.padding()
}
}
}
}
SecondView
struct SecondView: View {
#State private var isButtonVisible: Bool = false
#Binding var showButton: Bool
var body: some View {
List {
ForEach(0..<100) { _ in
Text("Hello World")
}
}
.safeAreaInset(edge: .bottom) {
if showButton {
HStack {
Spacer()
button
Spacer()
}
//I have added transparency, you can make it opaque if you want
.background(.cyan.opacity(0.8))
}
}
}
var button: some View {
Text("Something")
}
}
Try this if you don't want your list go under nav bar.
struct SecondView: View {
#State private var isButtonVisible: Bool = false
#State var showButton: Bool = true
var body: some View {
VStack {
List(0..<10) { _ in
Text("Hello World")
}
.padding(.top, 1)
if showButton {
button
}
}
.background(Color(.systemCyan))
.navigationTitle("This is a title")
}
var button: some View {
Text("Something")
}
}

Popover displaying inaccurate information inside ForEach

I'm having a problem where I have a ForEach loop inside a NavigationView. When I click the Edit button, and then click the pencil image at the right hand side on each row, I want it to display the text variable we are using from the ForEach loop. But when I click the pencil image for the text other than test123, it still displays the text test123 and I have absolutely no idea why.
Here's a video. Why is this happening?
import SwiftUI
struct TestPopOver: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var showThemeEditor = false
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.showThemeEditor = true
}
}
}
}
}
.popover(isPresented: $showThemeEditor) {
CustomPopOver(isShowing: $showThemeEditor, text: text)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}
}
}
struct CustomPopOver: View {
#Binding var isShowing: Bool
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.isShowing = false
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
This is a very common issue (especially since iOS 14) that gets run into a lot with sheet but affects popover as well.
You can avoid it by using popover(item:) rather than isPresented. In this scenario, it'll actually use the latest values, not just the one that was present when then view first renders or when it is first set.
struct EditItem : Identifiable { //this will tell it what sheet to present
var id = UUID()
var str : String
}
struct ContentView: View {
private var stringObjects = ["test123", "helloworld", "reddit"]
#State private var editMode: EditMode = .inactive
#State private var editItem : EditItem? //the currently presented sheet -- nil if no sheet is presented
#ViewBuilder
var body: some View {
NavigationView {
List {
ForEach(self.stringObjects, id: \.self) { text in
NavigationLink( destination: HStack{Text("Test!")}) {
HStack {
Text(text)
Spacer()
if self.editMode.isEditing {
Image(systemName: "pencil.circle").imageScale(.large)
.onTapGesture {
if self.editMode.isEditing {
self.editItem = EditItem(str: text) //set the current item
}
}
}
}
}
.popover(item: $editItem) { item in //item is now a reference to the current item being presented
CustomPopOver(text: item.str)
}
}
}
.navigationBarTitle("Reproduce Editing Bug!")
.navigationBarItems(leading: EditButton())
.environment(\.editMode, $editMode)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct CustomPopOver: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var text: String
var body: some View {
VStack(spacing: 0) {
HStack() {
Spacer()
Button("Cancel") {
self.presentationMode.wrappedValue.dismiss()
}.padding()
}
Divider()
List {
Section {
Text(text)
}
}.listStyle(GroupedListStyle())
}
}
}
I also opted to use the presentationMode environment property to dismiss the popover, but you could pass the editItem binding and set it to nil as well (#Binding var editItem : EditItem? and editItem = nil). The former is just a little more idiomatic.

How can I check a if screen is on the navigation stack?

Given this...
NavigationLink(destination: Text("Hello")) {
Text("Press")
}
And this...
.sheet(isPresented: $viewModel.showComplete) {
Text("Hello")
}
How can I make the sheet only open if a view opened by the NavigationLink doesn't currently exist?
You may access the isActive parameter of NavigationLink and use it in a custom binding to determine whether to open the sheet.
Here is a simple demo:
struct ContentView: View {
#State var showSheet = false
#State var linkActive = false
var binding: Binding<Bool> {
.init(get: {
showSheet && !linkActive
}, set: {
showSheet = $0
})
}
var body: some View {
NavigationView {
VStack {
NavigationLink(
destination: DetailView(showSheet: $showSheet),
isActive: $linkActive
) {
Text("Go to...")
}
Button("Open sheet") {
self.showSheet.toggle()
}
}
}
.sheet(isPresented: binding) {
Text("Hello")
}
}
}
struct DetailView: View {
#Binding var showSheet: Bool
var body: some View {
Button("Open sheet") {
self.showSheet.toggle()
}
}
}

SwiftUI: button in ToolbarItem(placement: .principal) not work after change it's label

Xcode 12 beta 6
There is a button in toolbar, its label text is binding to a state var buttonTitle. I want to tap this button to trigger a sheet view, select to change the binding var.
After back to content view, the button's title is updated. But if you tap the button again, it not work.
Code:
struct ContentView: View {
#State var show = false
#State var buttonTitle = "button A"
var body: some View {
NavigationView {
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .principal) {
Button {
show.toggle()
} label: {
Text(buttonTitle)
}
.sheet(isPresented: $show) {
SelectTitle(buttonTitle: $buttonTitle)
}
}
}
}
}
}
struct SelectTitle: View {
#Environment(\.presentationMode) var presentationMode
#Binding var buttonTitle: String
var body: some View {
Button("Button B") {
buttonTitle = "Button B"
presentationMode.wrappedValue.dismiss()
}
}
}
It is known toolbar-sheet layout issue, see also here. You can file another feedback to Apple.
Here is a workaround for your case - using callback to update toolbar item after sheet closed. Tested with Xcode 12b5.
struct ContentView: View {
#State var show = false
#State var buttonTitle = "button A"
var body: some View {
NavigationView {
Text("Hello World!")
.toolbar {
ToolbarItem(placement: .principal) {
Button {
show.toggle()
} label: {
Text(buttonTitle)
}
.sheet(isPresented: $show) {
SelectTitle(buttonTitle: buttonTitle) {
self.buttonTitle = $0
}
}
}
}
}
}
}
struct SelectTitle: View {
#Environment(\.presentationMode) var presentationMode
#State private var buttonTitle: String
let callback: (String) -> ()
init(buttonTitle: String, callback: #escaping (String) -> ()) {
_buttonTitle = State(initialValue: buttonTitle)
self.callback = callback
}
var body: some View {
Button("Button B") {
buttonTitle = "Button B"
presentationMode.wrappedValue.dismiss()
}
.onDisappear {
callback(buttonTitle)
}
}
}
Move sheet(...) outside of ToolbarItem scope like this:
NavigationView {
..
}.sheet(...)