SwiftUI rename SideBar item - swiftui

I'm using SwiftUI and create a SlideMenu, I want to rename my menu item. I do it by change Text to TextField but the TextField disabled by default. What wrong with TextField, I unable to click on it or is there any way to rename without swap Text <-> TextField?
struct CellView: View {
#State private var isRename = false
#State private var newName: String = ""
var menuItem: MenuItem
var body: some View {
HStack {
Image(systemName: "folder")
.foregroundColor(.accentColor)
if isRename {
TextField(menuItem.name ?? "Unknow name", text: $newName)
}else {
Text(menuItem.name ?? "Unknow name")
}
Spacer()
}
.contextMenu {
Button(action: {
isRename = true
}) {
HStack {
Image(systemName: "pencil")
.foregroundColor(.accentColor)
Text("Rename")
}
}
}
}
}

With iOS 16, there's a built-in RenameButton view and a .renameAction{} modifier:
RenameButton()
.renameAction {
//add action here
}

I don't think it is disabled (as tested provided code), you just see placeholder and TextField is not in focus by default.
So here is possible solution (based on CustomTextField from this answer)
if isRename {
CustomTextField(text: $newName,
nextResponder: .constant(nil),
isResponder: .constant(true),
isSecured: false,
keyboard: .default)
}else {
Text(menuItem.name ?? "Unknow name")
}
Spacer()
}
.contextMenu {
Button(action: {
newName = menuItem.name ?? "Unknown name"
isRename = true
}) {
Note: it is not shown what is MenuItem but consider possibility to bind text field directly to menuItem.name

Related

Picker not selecting the desired option

I am trying to setup a picker, simple. I am successfully fetching an array of projects from firebase and populating the picker with the names of the projects. The problem that I am having is that I need to get the project id when I click the list but it's not doing anything after I click the option that I want. I tried to run it in a simulator and also on my iPhone and nothing happens after I make the selection. I am pretty sure I am not updating the picker and thus I am not updating the variable with the selected project id. I tried using the .onChange on the picker but nothing happens.
import SwiftUI
struct NewProjectView: View {
#ObservedObject var viewModel = ProjectViewModel()
#ObservedObject var clientViewModel = ClientFeedViewModel()
#Environment (\.dismiss) var dismiss
#State var projectName: String = "s"
var clientNameIsEmpty: Bool {
if projectName.count < 3 {
return true
} else {
return false
}
}
var clients: [Client] {
return clientViewModel.clients
}
#State var selectedClient: String = ""
var body: some View {
NavigationView {
VStack {
Picker("", selection: $selectedClient) {
ForEach(clients, id:\.self) {
Text($0.clientName)
//I need to exctract the project id so I can pass it on
}
}
.pickerStyle(.menu)
CustomTextField(text: $projectName, placeholder: Text("Client Name"), imageName: "person.text.rectangle")
.padding()
.background(Color("JUMP_COLOR")
.opacity(0.75)
)
.cornerRadius(10)
.padding(.horizontal, 40)
Text("Name must contain more than 3 characters")
.font(.system(.subheadline))
.foregroundColor(.gray.opacity(0.3))
.padding(.top, 30)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
Button(action: {
dismiss()
}, label: {
Text("Cancel")
})
})
ToolbarItem(placement: .navigationBarTrailing , content: {
Button(action: {
viewModel.newProject(name: projectName)
dismiss()
}, label: {
Text("Save")
})
.disabled(clientNameIsEmpty)
})
}
}
}
.presentationDetents([.height(400)])
//.presentationDetents([.medium])
.presentationDragIndicator(.visible)
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}
Here is the picker populated with the foo data: picker
Your selection variable $selectedClient needs to have a type that matches the tagged value of each item in the picker.
As you're not specifying an explicit .tag for your text, the ForEach creates an implicit one using what it's using for tracking its loop, which in this case looks like it's a Client.
You can either change selectedClient to be a type of Client, or tag your displayed subview with the string value to populate selectedClient with, e.g.:
ForEach(clients, id: \.self) { client in
Text(client.clientName)
.tag(client.clientID)
}
Also, if each client has a unique ID, you're better off using that as ForEach's identifier than \.self. You can either specify id: \.clientID, etc., to use a single attribute – or you can add Identifiable conformance to Client and make sure that it has an id value that is guaranteed to be unique.
import SwiftUI
import Firebase
struct NewProjectView: View {
#ObservedObject var viewModel = ProjectViewModel()
#ObservedObject var clientViewModel = ClientFeedViewModel()
#Environment (\.dismiss) var dismiss
#State var projectName: String = "s"
var clientNameIsEmpty: Bool {
if projectName.count < 3 {
return true
} else {
return false
}
}
var clients: [Client] {
return clientViewModel.clients
}
#State var selectedClient: Client = Client(id: "", clientName: "Blank", timestamp: Timestamp(), ownerId: "", ownerUsername: "")
var body: some View {
NavigationView {
VStack {
Picker("d", selection: $selectedClient) {
ForEach(clients, id:\.id) { client in
Text(client.clientName)
.tag(client)
//I need to exctract the project id so I can pass it on
}
}
.pickerStyle(.menu)
Text(selectedClient.id ?? "")
CustomTextField(text: $projectName, placeholder: Text("Client Name"), imageName: "person.text.rectangle")
.padding()
.background(Color("JUMP_COLOR")
.opacity(0.75)
)
.cornerRadius(10)
.padding(.horizontal, 40)
Text("Name must contain more than 3 characters")
.font(.system(.subheadline))
.foregroundColor(.gray.opacity(0.3))
.padding(.top, 30)
.toolbar {
ToolbarItem(placement: .navigationBarLeading, content: {
Button(action: {
dismiss()
}, label: {
Text("Cancel")
})
})
ToolbarItem(placement: .navigationBarTrailing , content: {
Button(action: {
viewModel.newProject(name: projectName)
dismiss()
}, label: {
Text("Save")
})
.disabled(clientNameIsEmpty)
})
}
}
}
.presentationDetents([.height(400)])
//.presentationDetents([.medium])
.presentationDragIndicator(.visible)
}
}
struct NewProjectView_Previews: PreviewProvider {
static var previews: some View {
NewProjectView()
}
}

Adding a Navigation Link inside a menu [duplicate]

Can you use a NavigationLink as a Menu's item in swiftUI?
It seems to do just nothing:
Menu {
NavigationLink(destination: Text("test1")) {
Text("item1")
}
NavigationLink(destination: Text("test2")) {
Text("item2")
}
} label: {
Text("open menu")
}
In case it is meant to not work as tried above, is there an alternative way of achiving the intended reult?
init(destination:isActive:label:) is deprecated since iOS 16
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use
NavigationLink(value:label:) inside a NavigationStack or
NavigationSplitView
NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)
struct DemoNavigateFromMenu: View {
#State private var navigateTo = ""
#State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}
I can say that Asperi's answer is great solution. It helped a lot. But we need a custom view to hold a reference inside the destination property right? not a string.
#State var navigateTo: AnyView?
#State var isNavigationActive = false
We can hold a reference AnyView type and then call the view like this:
Menu {
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create an Item", systemImage: "doc")
}
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create a category", systemImage: "folder")
}
} label: {
Label("Add", systemImage: "plus")
}
For more detail please see this post:
https://developer.apple.com/forums/thread/119583

SwiftUI: How to push to new view from Menu Buttons [duplicate]

Can you use a NavigationLink as a Menu's item in swiftUI?
It seems to do just nothing:
Menu {
NavigationLink(destination: Text("test1")) {
Text("item1")
}
NavigationLink(destination: Text("test2")) {
Text("item2")
}
} label: {
Text("open menu")
}
In case it is meant to not work as tried above, is there an alternative way of achiving the intended reult?
init(destination:isActive:label:) is deprecated since iOS 16
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use
NavigationLink(value:label:) inside a NavigationStack or
NavigationSplitView
NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)
struct DemoNavigateFromMenu: View {
#State private var navigateTo = ""
#State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}
I can say that Asperi's answer is great solution. It helped a lot. But we need a custom view to hold a reference inside the destination property right? not a string.
#State var navigateTo: AnyView?
#State var isNavigationActive = false
We can hold a reference AnyView type and then call the view like this:
Menu {
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create an Item", systemImage: "doc")
}
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create a category", systemImage: "folder")
}
} label: {
Label("Add", systemImage: "plus")
}
For more detail please see this post:
https://developer.apple.com/forums/thread/119583

Crash when using hiding a ProgressView in a Picker label

I am trying to put a ProgressView inside a Picker label. When I tap the Hide Spinner button, this (intermittently) crashes with EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
struct ContentView: View {
#State private var selectedCity = ""
#State private var showSpinner = true
let cities = [
"Calgary",
"Edmonton",
"Toronto"
]
var body: some View {
NavigationView {
VStack(spacing: 0) {
Form {
Picker(selection: $selectedCity, label:
HStack {
Text("Your City")
if showSpinner {
ProgressView()
.padding(.horizontal, 2)
}
}
) {
ForEach(cities, id: \.self) { city in
Text(city).tag(city)
}
}
Button("Hide Spinner", action: { showSpinner = false })
}
}
.navigationBarTitle("ProgressView Crash", displayMode: .inline)
}
}
}
Am I doing anything wrong? I'm guessing this is a SwiftUI bug. I get the same behaviour when wrapping a UIActivityIndicatorView in a UIViewRepresentable.
Yes, it looks like a bug with auto-generated accessibility label. The safe workaround is to use explicitly provided accessibility.
Tested with Xcode 12 / iOS 14
Picker(selection: $selectedCity, label:
HStack {
Text("Your City")
if showSpinner {
ProgressView()
.padding(.horizontal, 2)
}
}.accessibility(label: Text("Your City")) // << here !!
) {
ForEach(cities, id: \.self) { city in
Text(city).tag(city)
}
}

SwiftUI: Add ClearButton to TextField

I am trying to add a ClearButton to TextField in SwiftUI when the particular TextField is selected.
The closest I got was creating a ClearButton ViewModifier and adding it to the TextField using .modifer()
The only problem is ClearButton is permanent and does not disappear when TextField is deselected
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier {
#Binding var text: String
public func body(content: Content) -> some View {
HStack {
content
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
}
}
}
}
Use ZStack to position the clear button appear inside the TextField.
TextField("Some Text" , text: $someBinding).modifier(ClearButton(text: $someBinding))
struct ClearButton: ViewModifier
{
#Binding var text: String
public func body(content: Content) -> some View
{
ZStack(alignment: .trailing)
{
content
if !text.isEmpty
{
Button(action:
{
self.text = ""
})
{
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
.padding(.trailing, 8)
}
}
}
}
Use .appearance() to activate the button
var body: some View {
UITextField.appearance().clearButtonMode = .whileEditing
return TextField(...)
}
For reuse try with this:
func TextFieldUIKit(text: Binding<String>) -> some View{
UITextField.appearance().clearButtonMode = .whileEditing
return TextField("Nombre", text: text)
}
=== solution 1(best): Introspect https://github.com/siteline/SwiftUI-Introspect
import Introspect
TextField("", text: $text)
.introspectTextField(customize: {
$0.clearButtonMode = .whileEditing
})
=== solution 2: ViewModifier
public struct ClearButton: ViewModifier {
#Binding var text: String
public init(text: Binding<String>) {
self._text = text
}
public func body(content: Content) -> some View {
HStack {
content
Spacer()
Image(systemName: "multiply.circle.fill")
.foregroundColor(.secondary)
.opacity(text == "" ? 0 : 1)
.onTapGesture { self.text = "" } // onTapGesture or plainStyle button
}
}
}
Usage:
#State private var name: String
...
Form {
Section() {
TextField("NAME", text: $name).modifier(ClearButton(text: $name))
}
}
=== solution 3: global appearance
UITextField.appearance().clearButtonMode = .whileEditing
You can add another Binding in your modifier:
#Binding var visible: Bool
then bind it to opacity of the button:
.opacity(visible ? 1 : 0)
then add another State for checking textField:
#State var showClearButton = true
And lastly update the textfield:
TextField("Some Text", text: $someBinding, onEditingChanged: { editing in
self.showClearButton = editing
}, onCommit: {
self.showClearButton = false
})
.modifier( ClearButton(text: $someBinding, visible: $showClearButton))
Not exactly what you're looking for, but this will let you show/hide the button based on the text contents:
HStack {
if !text.isEmpty {
Button(action: {
self.text = ""
}) {
Image(systemName: "multiply.circle")
}
}
}
After initializing a new project we need to create a simple view modifier which we will apply later to our text field. The view modifier has the tasks to check for content in the text field element and display a clear button inside of it, if content is available. It also handles taps on the button and clears the content.
Let’s have a look at that view modifier:
import SwiftUI
struct TextFieldClearButton: ViewModifier {
#Binding var text: String
func body(content: Content) -> some View {
HStack {
content
if !text.isEmpty {
Button(
action: { self.text = "" },
label: {
Image(systemName: "delete.left")
.foregroundColor(Color(UIColor.opaqueSeparator))
}
)
}
}
}
}
The code itself should be self explanatory and easy to understand as there is no fancy logic included in our tasks.
We just wrap the textfield inside a HStack and add the button, if the text field is not empty. The button itself has a single action of deleting the value of the text field.
For the clear icon we use the delete.left icon from the SF Symbols 2 library by Apple, but you could also use another one or even your own custom one.
The binding of the modifier is the same as the one we apply to the text field. Without it we would not be able to check for content or clear the field itself.
Inside the ContentView.swift we now simply add a TextField element and apply our modifier to it — that’s all!
import SwiftUI
struct ContentView: View {
#State var exampleText: String = ""
var body: some View {
NavigationView {
Form {
Section {
TextField("Type in your Text here...", text: $exampleText)
.modifier(TextFieldClearButton(text: $exampleText))
.multilineTextAlignment(.leading)
}
}
.navigationTitle("Clear button example")
}
}
}
The navigation view and form inside of the ContentView are not required. You could also just add the TextField inside the body, but with a form it’s much clearer and beautiful. 🙈
And so our final result looks like this:
I found this answer from #NigelGee on "Hacking with Swift".
.onAppear {
UITextField.appearance().clearButtonMode = .whileEditing
}
It really helped me out.
Simplest solution I came up with
//
// ClearableTextField.swift
//
// Created by Fred on 21.11.22.
//
import SwiftUI
struct ClearableTextField: View {
var title: String
#Binding var text: String
init(_ title: String, text: Binding<String>) {
self.title = title
_text = text
}
var body: some View {
ZStack(alignment: .trailing) {
TextField(title, text: $text)
Image(systemName: "xmark.circle.fill")
.foregroundColor(.secondary)
.onTapGesture {
text = ""
}
}
}
}
struct ClearableTextField_Previews: PreviewProvider {
#State static var text = "some value"
static var previews: some View {
Form {
// replace TextField("Original", text: $text) with
ClearableTextField("Clear me", text: $text)
}
}
}