Title for Keyboard Shortcut in SwiftUI - swiftui

I want to change the discoverability title of a keyboard shortcut in SwiftUI.
As you can see below the title shows in the popup if used in text button, but if you use an image for the button it doesn't show in the popup (when holding cmd on the keyboard to view supported shortcuts by the app).
struct ContentView: View {
var body: some View {
VStack {
Button("Save to Favorites") {
}
.keyboardShortcut("a")
Button {
} label: {
Image(systemName: "heart.fill")
}
.keyboardShortcut("s")
}
}
}
How can I add a title to the shortcuts help popup?
Note that I have tried all accessibility stuff, i.e. label, identifier, hint, etc... and It didn't work.

Not a super elegant solution but I got it working by adding a Text with a .frame size of width: 0, height: 0. This effectively hides the Text from view but ensures it appears when the user holds down the ⌘ key.
Consider putting it in a ZStack too as the default arrangement could have it ever so slightly off centre.
VStack {
Button("Save to Favorites") {
}
.keyboardShortcut("a")
Button {
} label: {
ZStack {
Text("heart")
.frame(width: 0, height: 0) // <- this part
Image(systemName: "heart.fill")
}
}
.keyboardShortcut("s")
}

Related

ScrollViewReader / scrollTo(_:anchor:) not working reliably

I've build a ScrollView which contains 0-3 images and a multiline text field in a VStack. I also added a ScrollViewReader inside the scrollview and use it to scroll to the bottom of the text field upon certain events (user starts typing, image collection changes).
The point is: sometimes it works, sometimes it doesn't. When it does not work I realized, that when I scroll a little bit by hand and then try again (e.g. typing) it works.
Not sure if this is relevant, but ImageOrPlaceholderComponent first shows a placeholder as long as the image within currentEntryImages is nil, and the image after that (both states imply a change to currentEntryImages and should thus result in scrolling to the bottom of the text field).
NavigationStack {
ScrollView {
ScrollViewReader { scrollview in
VStack {
// Attached images.
AnyLayout(VStackLayout(spacing: 2.5)) {
ForEach(values: currentEntryImages) { entryImage in
ImageOrPlaceholderComponent(image: entryImage)
.clipped()
}
}
// Text field for the entry with toolbar.
TextField("...", text: $entryDTO.text, axis: .vertical)
.id(entryTextFieldAnchor)
.multilineTextAlignment(.leading)
.padding()
.focused($mainTextFieldFocused)
.onAppear { mainTextFieldFocused = true }
// Scroll to the bottom of the text field, when the user is typing ...
.onChange(of: entryDTO.text) { _ in
withAnimation {
scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
}
}
// ... or the entry images have changed.
.onChange(of: currentEntryImages) { _ in
withAnimation {
scrollview.scrollTo(entryTextFieldAnchor, anchor: .bottom)
}
}
}
}
}
}

Does Xcode SwiftUI button default layout has border?

I am using Xcode to create an app that requires buttons. Right now, when I create a button, I get the text label, which I want, but I also get a background with rounded corners around it. I want to have the button with just the label but without the background. I was using Swift Playgrounds before Xcode and did not have this problem.
Here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
ZStack {
Rectangle()
.frame(width: 1000, height: 500)
.foregroundColor(.red)
Button(action: {
}) {
Text("Button")
}
}
}
}
Customize the button's style with the .buttonStyle view modifier:
Button(action: {}) {
Text("Button")
}
.buttonStyle(.borderless)
.borderless, .plain, and .link are the options that will result in no border.
Here is a Button without any background or border.
Button("Click") {
//do something
}
.background(.clear) //this

How to get a horizontal ScrollView in SwiftUI to scroll automatically to end when the enclosed Text is updated

I am writing a SwiftUI iOS app where I need a Text view to automatically scroll to the end of its content whenever the content is updated. The update happens from the model. To not complicate this question with the details of my app, I have created a simple scenario where I have two text fields and a text label. Any text entered in the text fields is concatenated and shown in the text label. The text label is enclosed in a horizontal ScrollView and can be scrolled manually if the text is longer than the screen width. What I want to achieve is for the text to scroll to the end automatically whenever the label is updated.
Here is the simple model code:
class Model: ObservableObject {
var firstString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
var secondString = "" {
didSet { combinedString = "\(firstString). \(secondString)." }
}
#Published var combinedString = ""
}
This is the ContentView:
struct ContentView: View {
#ObservedObject var model: Model
var body: some View {
VStack(alignment: .leading, spacing: 10) {
TextField("First string: ", text: $model.firstString)
TextField("Second string: ", text: $model.secondString)
Spacer().frame(height: 20)
Text("Combined string:")
ScrollView(.horizontal) {
Text(model.combinedString)
}
}
}
}
From the research I have done, the only way I have found to scroll to the end of the text, without having to do it manually, is to add a button to the view, which causes the text in the label to scroll to the end.
Here is the above ScrollView embedded in a ScrollViewReader, with a button to effect the scrolling action.
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
Button("Scroll to end") {
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
.padding()
.foregroundColor(.white)
.background(Color.black)
}
}
This works, provided the intention is to use a button to effect the scrolling action.
My question is: Can the scrolling action above be triggered whenever the model is updated, without the need to click a button.
Any help or pointers will be much appreciated.
Thanks.
I assume you wanted this:
ScrollViewReader { scrollView in
VStack {
ScrollView(.horizontal) {
Text(model.combinedString)
.id("combinedText")
}
.onChange(of: model.combinedString) { // << here !!
withAnimation {
scrollView.scrollTo("combinedText", anchor: .trailing)
}
}
}
}
ScrollViewReader is the solution you're looking for. You may need to play around with the value. Also you'll need to add the .id(0) modifier to your textview.
ScrollView {
ScrollViewReader { reader in
Button("Go to first then anchor trailing.") {
value.scrollTo(0, anchor: .trailing)
}
// The rest of your code .......

SwiftUI - Visual Feedback when Expanding Collapsed View

I am working on a List view in SwiftUI. I have a section at the bottom that is collapsed when presented. Clicking on the section header expands the list below the tab bar, but there is no visual indication to the user.
Is there an easy way to force the view to scroll after expanding the section, so the user sees there are additional rows?
Thanks
var body: some View {
Section(header: Text("Archived").onTapGesture {
self.sectionState.toggle()
}) {
if sectionState {
ForEach(filteredArchives) { listVM in
NavigationLink(destination: ListViewDetail(listVM: listVM)) {
ListView(listVM: listVM)
.frame(width: nil, height: 75)
}
}
}
}
}

How to Hide Keyboard in SwiftUI Form Containing Picker?

I have a SwiftUI Form that contains a Picker, a TextField, and a Text:
struct ContentView: View {
var body: some View {
Form {
Section {
Picker(selection: $selection, label: label) {
// Code to populate picker
}.pickerStyle(SegmentedPickerStyle())
HStack {
TextField(title, text: $text)
Text(text)
}
}
}
}
}
The code above results in the following UI:
I am able to easily select the second item in the picker, as shown below:
Below, you can see that I am able to initiate text entry by tapping on the TextField:
In order to dismiss the keyboard when the Picker value is updated, a Binding was added, which can be seen in the following code block:
Picker(selection: Binding(get: {
// Code to get selected segment
}, set: { (index) in
// Code to set selected segment
self.endEditing()
}), label: label) {
// Code to populate picker
}
The call to self.endEditing() is provided in the following method:
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
The following screenshot displays that selecting a different segment of the Picker dismisses the keyboard:
Up to this point, everything works as expected. However, I would like to dismiss the keyboard when tapping anywhere outside of the TextField since I am unable to figure out how to dismiss the keyboard when dragging the Form's containing scroll view.
I attempted to add the following implementation to dismiss the keyboard when tapping on the Form:
Form {
Section {
// Picker
HStack {
// TextField
// Text
}
}
}.onTapGesture {
self.endEditing()
}
Below, the following two screenshot displays that the TextField is able to become the first responder and display the keyboard. The keyboard is then successfully dismissed when tapping outside of the TextField:
However, the keyboard will not dismiss when attempting to select a different segment of the `Picker. In fact, I cannot select a different segment, even after the keyboard has been dismissed. I presume that a different segment cannot be selected because the tap gesture attached to the form is preventing the selection.
The following screenshot shows the result of attempting to select the second value in the Picker while the keyboard is shown and the tap gesture is implemented:
What can I do to allow selections of the Picker's segments while allowing the keyboard to be dismissed when tapping outside of the TextField?
import SwiftUI
struct ContentView: View {
#State private var tipPercentage = 2
let tipPercentages = [10, 15, 20, 25, 0]
#State var text = ""
#State var isEdited = false
var body: some View {
Form {
Section {
Picker("Tip percentage", selection: $tipPercentage) {
ForEach(0 ..< tipPercentages.count) {
Text("\(self.tipPercentages[$0])%")
}
}
.pickerStyle(SegmentedPickerStyle())
HStack {
TextField("Amount", text: $text, onEditingChanged: { isEdited in
self.isEdited = isEdited
}).keyboardType(.numberPad)
}
}
}.gesture(TapGesture().onEnded({
UIApplication.shared.windows.first{$0.isKeyWindow }?.endEditing(true)
}), including: isEdited ? .all : .none)
}
}
Form's tap gesture (to finish editing by tap anywhere) is enabled only if text field isEdited == true
Once isEdited == false, your picker works as before.
You could place all of your code in an VStack{ code }, add a Spacer() to it and add the onTap to this VStack. This will allow you to dismiss the keyboard by clicking anywhere on the screen.
See code below:
import SwiftUI
struct ContentView: View {
#State private var text: String = "Test"
var body: some View {
VStack {
HStack {
TextField("Hello World", text: $text)
Spacer()
}
Spacer()
}
.background(Color.red)
.onTapGesture {
self.endEditing()
}
}
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Changing the background color of an HStack or VStack to red simplifies figuring out where the user may click to dismiss.
Copy and paste code for a ready to run example.