SwiftUI iOS15 Toolbar's content not refreshing based on the FocusState value - ios

I have a view with two TextFields. When the first one is focused, I'd like to display Next button in the toolbar and when the second text field is focused, I'd like to present Previous and Done buttons in the toolbar.
I have an if statement inside the toolbar, but it looks like it doesn't pick up the change of #FocusState until I type something.
Any ideas how to make it work properly or why doesn't the toolbar pick up the changes?
This is more or less the code (I simplified the actual code):
import SwiftUI
import Combine
enum Field {
case inLangName
case outLangName
}
struct MyView: View {
#FocusState private var focusedTextField: Field?
#State var inLangName: String = ""
#State var outLangName: String = ""
var body: some View {
NavigationView {
VStack(spacing: 15) {
TextField("In lang name", text: $inLangName)
.focused($focusedTextField, equals: .inLangName)
.simultaneousGesture(TapGesture().onEnded { _ in
focusedTextField = .inLangName
})
TextField("Out lang name", text: $outLangName)
.focused($focusedTextField, equals: .outLangName)
.simultaneousGesture(TapGesture().onEnded { _ in
focusedTextField = .outLangName
})
}
.navigationBarHidden(true)
.navigationBarTitleDisplayMode(.inline)
.onAppear {
if inLangName.isEmpty {
focusedTextField = .inLangName
}
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
if focusedTextField == .inLangName || focusedTextField == nil {
Spacer()
Button(action: {
focusedTextField = .outLangName
}) {
Text("next")
}
} else if focusedTextField == .outLangName {
Button(action: {
focusedTextField = .inLangName
}) {
Text("previous")
}
Spacer()
Button(action: {
//onDoneButtonClicked()
}) {
Text("done")
}
}
}
}
}
}
}

Related

How do I show different buttons in the toolbar based on which field is focused in SwiftUI?

I want to show different buttons based on what field is focused, but the focus state is allways one behind.
Here is the code:
struct ContentView: View {
enum FocusField {
case one
case two
}
#FocusState var focused: FocusField?
var body: some View {
VStack {
TextField("One", text: .constant(""))
.focused($focused, equals: .one)
TextField("Two", text: .constant(""))
.focused($focused, equals: .two)
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
if focused == .one {
Text("One")
} else if focused == .two {
Text("Two")
} else {
Text("None")
}
Spacer()
Button("Done") { focused = nil }
}
}
}
}
#FocusState can be a bit buggy at times. I could only speculate on the reason why your approach is not working. But consider this solution (tested on Xcode 13 and IOS 15):
struct ContentView33: View {
// add confomance to String
enum FocusField: String {
case one = "One" // add rawValues
case two = "Two"
}
#FocusState var focused: FocusField?
var body: some View {
VStack {
TextField("One", text: .constant(""))
.focused($focused, equals: .one)
TextField("Two", text: .constant(""))
.focused($focused, equals: .two)
}
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
//this will update the keyboard toolbar
Text(focused?.rawValue ?? "None")
Spacer()
Button("Done") { focused = nil }
}
}
}
}

ToolbarItemGroup in .toolbar {} doesn't work in a Sheet

I'm using SwiftUI 3.0, Swift 5.5 and Xcode 13.2, tested on iOS 15.3 iPhone device, and iOS 15.2 iPhone simulator.
I have tested the following.
This is a view, with a TextField, a focused state and a .toolbar
import SwiftUI
struct test: View {
#State private var name = "Taylor Swift"
#FocusState var isInputActive: Bool
var body: some View {
TextField("Enter your name", text: $name)
.textFieldStyle(.roundedBorder)
.focused($isInputActive)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button(name) {
isInputActive = false
}
}
}
}
}
struct test_Previews: PreviewProvider {
static var previews: some View {
test()
}
}
It works perfectly as expected and it shows a button, with whatever text is typed in the TextField.
Then, when it's displayed in a sheet, there is no toolbar, though it is the same code. This is the sheet example:
import SwiftUI
struct test: View {
#State private var name = "Taylor Swift"
#FocusState var isInputActive: Bool
#State var isSheetPresented: Bool = false
var body: some View {
VStack {
Button {
self.isSheetPresented = true
} label: {
Text("Open Sheet")
}
}
.sheet(isPresented: $isSheetPresented) {
TextField("Enter your name", text: $name)
.textFieldStyle(.roundedBorder)
.focused($isInputActive)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button(name) {
isInputActive = false
}
}
}
}
}
}
struct test_Previews: PreviewProvider {
static var previews: some View {
test()
}
}
Toolbar needs a
NavigationView
And one at the top level. Surrounding the text field.
Today I also experienced the same thing. I had to spent many hours until finally got the solution after reading this question. I wanted to put "Done" button over my keyboard for dismissing it after editing is finish and I used ToolbarItem(placement: .keyboard).
In my case, I mistakenly put more than one .toolbar() in different places. And it causing the "Done" button in my sheet becoming disabled, something like this (in simulator):
In order to solve the problem, please DO NOT do this:
struct SettingsView: View {
var body: some View {
NavigationView {
Form {
// Some other codes..
}.navigationBarTitle("Settings", displayMode: .large).toolbar() { // <--- This one is a .toolbar()
ToolbarItem{
Button("Cancel"){
self.mode.wrappedValue.dismiss()
}
}}
}.toolbar { // <--- This one another .toolbar() (-_-")
ToolbarItem(placement: .keyboard) { // <--- This one is in the WRONG place!
Button("Done") {
focusedField = nil
}
}
}
}
}
Instead, do the following:
struct SettingsView: View {
var body: some View {
NavigationView {
Form {
// Some other codes..
}.navigationBarTitle("Settings", displayMode: .large).toolbar() { // Make it into a single .toolbar() 👍🏼
ToolbarItem{
Button("Cancel"){
self.mode.wrappedValue.dismiss()
}
}
ToolbarItem(placement: .keyboard) {
Button("Done") {
focusedField = nil
}
}
}
}
}
}
Hope it helps.

SwiftUI - Present 3 different Views with different parameter

I need to present 3 different Views.
AddListView
ChangeColor
EditListView
They take different paramater. AddListView does not have parameter while ChangeColor and EditListView takes Color and NSManagedObject respectively. However for the sake of simplicity, EditListView's paramter is integer in this example.
I am using .fullScreenCover(item: <#T##Binding<Identifiable?>#>, content: <#T##(Identifiable) -> View#>) for presenting them.
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
}
else if type == .EditListView {
if let index = selectedIndex {
EditListView(index: index)
}
}
else if type == .ChangeColor {
if let color = selectedColor {
ColorView(color: color)
}
}
}
selectedIndex and selectedColor is nil even though I initialize them before initializing presentedViewType. And hence, an EmptyView is presented.
This is the project.
enum PresentedViewType: Identifiable {
case AddListView
case ChangeColor
case EditListView
var id: Int {
return hashValue
}
}
struct ContentView: View {
#State var presentedViewType: PresentedViewType?
#State var selectedColor: Color?
#State var selectedIndex: Int?
var body: some View {
NavigationView {
List {
Section {
NavigationLink(destination: Text("All")) {
Text("All")
}
.background(Color.blue)
.contextMenu {
Button(action: {
selectedColor = .blue
presentedViewType = .ChangeColor
}) {
Label("Change Color", systemImage: "paintbrush.pointed.fill")
}
}
}
ForEach(0..<10) { index in
NavigationLink(destination: Text("Row Details \(index)")) {
Text("Row \(index)")
}
.contextMenu {
Button(action: {
selectedIndex = index
presentedViewType = .EditListView
}) {
Label("Edit", systemImage: "square.and.pencil")
}
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
presentedViewType = .AddListView
}) {
Label("Add", systemImage: "plus")
}
}
}
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
}
else if type == .EditListView {
if let index = selectedIndex {
EditListView(index: index)
}
}
else if type == .ChangeColor {
if let color = selectedColor {
ColorView(color: color)
}
}
}
}
}
}
struct ColorView: View {
#Environment(\.presentationMode) var presentationMode
#State var color: Color
var body: some View {
NavigationView {
Text("Color View")
.background(color)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
struct AddListView: View {
#Environment(\.presentationMode) var presentationMode
#State var text: String = ""
var body: some View {
NavigationView {
TextField("", text: $text)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
struct EditListView: View {
#Environment(\.presentationMode) var presentationMode
#State var index: Int
var body: some View {
NavigationView {
Text("Row \(index)")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: {
presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "xmark")
}
}
}
}
}
}
}
I have to mention that they do not have fixed value. They have different value depending on which row you need to edit.
How to pass selectedIndex and selectedColor to EditListView and ColorView respectively?
Update
EditListView takes only selectedIndex while ColorView takes only selectedColor
You need to have #Binding properties inside EditListView and ColorView
struct EditListView: View {
#Binding var selectedIndex: Int?
// rest of view implementation
}
struct ColorView: View {
#Binding var selectedIndex: Int?
// rest of view implementation
}
and then pass the binding in the initialisers
.fullScreenCover(item: $presentedViewType) { type in
if type == .AddListView {
AddListView()
} else if type == .EditListView {
EditListView(index: $selectedIndex)
} else if type == .ChangeColor {
ColorView(color: $selectedColor)
}
}

How to localize EditButton in SwiftUI?

The EditButton https://developer.apple.com/documentation/swiftui/editbutton in SwiftUI seems can only display in English. Is there a way we can localize the text in the button?
NavigationView {
List {
}
.navigationBarTitle("Test")
.navigationBarItems(leading: EditButton(), trailing: Button(action: {
}) {
Image(systemName: "plus")
})
}
Create your own EditButton might be the way to go for now
struct NewEditButton: View {
#Binding var editMode: EditMode
var onDone: (() -> Void)?
var body: some View {
Button(action: {
if self.editMode.isEditing {
self.editMode = .inactive
self.onDone?()
} else {
self.editMode = .active
}
}) {
self.editMode == .active ? Text(NSLocalizedString("active", comment: "active")) : Text(NSLocalizedString("edit", comment: "edit"))
}
}
}
and then the external editMode state can be used to set the current editing model In SwiftUI how do I set the environment variable of editMode in an XcodePreview

Long press of NavigationView only work on the left part, not all the NavigationLink?

Following is a NavigationView, the view pops to Destination2 when long press the NavigationLink and to Destination1 when normally tap it. But the right zone of the NavigationLink in the picture cannot be long pressed.
Does anyone know the reason? Thanks!
import SwiftUI
struct ContentView: View {
#State private var isLongPressed = false
#State var currentTag: Int?
let lyrics = ["OutNotWorkA", "OutNotWorkB", "OutNotWorkC"]
var body: some View {
NavigationView {
List {
ForEach(0..<lyrics.count) { index in
VStack{
HStack(alignment: .top) {
NavigationLink(destination: Group
{ if self.isLongPressed { Destination2() } else { Destination1() } }, tag: index, selection: self.$currentTag
) {
Text(self.lyrics[index])
}
}
}.simultaneousGesture(LongPressGesture().onEnded { _ in
print("Got Long Press")
self.currentTag = index
self.isLongPressed = true
})
.simultaneousGesture(TapGesture().onEnded{
print("Got Tap")
self.currentTag = index
self.isLongPressed = false
})
.onAppear(){
self.isLongPressed = false
}
}
}
}
}
}
struct Destination1: View {
var body: some View {
Text("Destination1")
}
}
struct Destination2: View {
var body: some View {
Text("Destination2")
}
}
Then how to handle the whole part?
Find below the fix
VStack{
HStack(alignment: .top) {
NavigationLink(destination: Group
{ if self.isLongPressed { Destination2() } else { Destination1() } }, tag: index, selection: self.$currentTag
) {
Text(self.lyrics[index])
}
}
}
.contentShape(Rectangle()) // << here !!
.simultaneousGesture(LongPressGesture().onEnded { _ in
LongPressGesture only works on the visualized part of the label.
The easiest way to handle this problem is a little workaround with a lot of spaces:
Text(self.lyrics[index]+" ")
Because only using spaces doesn't create a line break this makes no visual problems in your App.

Resources