How to localize EditButton in SwiftUI? - localization

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

Related

List editMode behavior broken on iPadOS 16 only

I am wondering if anyone else is seeing this behavior. I have an app that builds for iPadOS 14-16 only where editMode behavior is broken in iOS 16 only. We have a custom edit button design so using the default one(which seems to be the only way to get the drag and drop icon to show) is not an option. Only after dragging a cell or when there are a lot of cells and you scroll off screen does the drag and drop icon show. Using the following code:
struct Number: Identifiable {
let id: String
let number: Int
}
struct ContentView: View {
#State var testData = Array(1..<10).map { Number(id: UUID().uuidString, number: $0) }
#State var editMode: EditMode = .inactive
#State var isEditing: Bool = false
var body: some View {
NavigationView {
List {
ForEach(testData) {
TestCellView(title: "\($0.number)")
}
.onMove(perform: editMode == .active ? moveRow : nil)
}
.listStyle(PlainListStyle())
.padding()
.navigationBarTitle("Hello")
.navigationBarItems(leading: Button(editMode == .active ? "Done" : "Edit", action: {
editMode = editMode == .active ? .inactive : .active
}))
.environment(\.editMode, $editMode)
}
}
private func moveRow(from source: IndexSet, to destination: Int) {
withAnimation {
testData.move(fromOffsets: source, toOffset: destination)
}
}
}
I have been beating my head against the wall with no results so far.
Instead of creating your own button, how about using the .buttonStyle modifier, and customising the standard EditButton, e.g.
struct EditButtonStyle: ButtonStyle {
#Environment(\.editMode) var editMode
func makeBody(configuration: Configuration) -> some View {
switch editMode?.wrappedValue {
case .active:
Image(systemName: "checkmark.square")
.foregroundColor(.accentColor)
default:
Image(systemName: "square.and.pencil")
.foregroundColor(.accentColor)
}
}
}
extension ButtonStyle where Self == EditButtonStyle {
static var edit: Self {
EditButtonStyle()
}
}
This simplifies your List…
struct ContentView: View {
#State var testData = Array(1..<10).map { Number(id: UUID().uuidString, number: $0) }
var body: some View {
NavigationView {
List {
ForEach(testData) {
Text("\($0.number)")
}
.onMove(perform: moveRow)
}
.listStyle(PlainListStyle())
.padding()
.navigationBarTitle("Hello")
.navigationBarItems(leading: EditButton().buttonStyle(.edit))
}
}
private func moveRow(from source: IndexSet, to destination: Int) {
testData.move(fromOffsets: source, toOffset: destination)
}
}
Based on the design of our app the above solutions didn't work for us as iPadOS allows editing of a list even when editMode is not .active. So we ended up going with a hack/workaround where we show one list that edits/one list that is read-only and switch back and forth.
Something like the code below:
List {
if editMode == .active {
ForEach(testData) {
TestCellView(title: "\($0.number)")
}
.onMove(perform: moveRow)
} else {
ForEach(testData) {
TestCellView(title: "\($0.number)")
}
}
}
.environment(\.editMode, $editMode)
.listStyle(PlainListStyle())
.padding()
.navigationBarTitle("Hello")
.navigationBarItems(leading: Button(editMode == .active ? "Done" : "Edit", action: {
editMode = editMode == .active ? .inactive : .active
}))
.animation(nil, value: editMode.isEditing)
It isn't the best solution but it gets the job done.

Animation not working inside sheet for swiftui

I am using a sheet to present a list of options and on click of the option I want to change the view with the animation of sliding from trailing. As per my understanding and what I have read on various sites I have written this code but I am not sure why it is not working the way intended. I just want to know where exactly this code went wrong.
struct XYZ: App {
let persistenceController = PersistenceController.shared
#State var isPresented : Bool = false
#State var isSwiped : Bool = false
var body: some Scene {
WindowGroup {
optionList(isPresented: $isPresented)
.sheet(isPresented: $isPresented, content: {
Text("This is from modal view!")
.onTapGesture {
withAnimation(Animation.easeIn(duration: 10)){
isSwiped.toggle()
}
}
if isSwiped {
checkedList()
.transition(.move(edge: .trailing))
}
})
}
}
}
struct optionList : View {
#Binding var isPresented : Bool
var body: some View {
Text("This is a testView")
.onTapGesture {
withAnimation{
isPresented.toggle()
}
}
}
}
struct checkedList : View {
#State var name : String = "WatchList"
var arr = ["First", "Second", "Third", "Fourth", "Fifth", "Sixth", "Seventh"]
#State var mp : [Int:Int] = [:]
var body: some View {
VStack{
HStack{
TextField("WatchlistName", text: $name)
.padding(.all)
Image(systemName: "trash.fill")
.padding(.all)
.onTapGesture {
print("Delete watchList!!")
}
}
ScrollView{
ForEach(arr.indices) { item in
HStack (spacing: 0) {
Image(systemName: mp.keys.contains(item) ? "checkmark.square" : "square")
.padding(.horizontal)
Text(arr[item])
}
.padding(.bottom)
.frame(width: UIScreen.main.bounds.width, alignment: .leading)
.onTapGesture {
if mp.keys.contains(item) {
mp[item] = nil
} else {
mp[item] = 1
}
}
}
}
Button {
print("Remove Ticked Elements!")
deleteWatchListItem(arr: Array(mp.keys))
} label: {
Text("Save")
}
}
}
func deleteWatchList(ind: Int){
print(ind)
}
func deleteWatchListItem(arr : [Int]) {
print(arr)
}
}
I tried to create a view and with the animation using withanimation with a bool variable tried to change the view.
It sounds like what you want is to push the checkedList on to a NavigationStack…
struct ContentView: View {
#State var isPresented : Bool = false
var body: some View {
Text("This is a testView")
.onTapGesture {
isPresented.toggle()
}
.sheet(isPresented: $isPresented, content: {
NavigationStack {
NavigationLink("This is from modal view!") {
checkedList()
}
}
})
}
}

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

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")
}
}
}
}
}
}
}

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)
}
}

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