SwiftUI - Visual Feedback when Expanding Collapsed View - swiftui

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

Related

Combining Navigation Bar, TabView and searchable causes the NavigationBar UI issue with lists

Combining Navigation Bar, TabView and searchable causes the NavigationBar and Search InputField to stay stationary when scrolling up on the second selected tab.
If I run the code below and first click on the Bookmark tab and scroll the list up, I get the desired results as shown in Figure 1.
If I immediately click the Home tab after the Bookmark tab and scroll the list up, I get an undesirable effect of the list displaying underneath the navigation header as shown in Figure 2.
The order that you click on the tabs produces different effects, and the position you last left the list before going to the next tab also has some strange influence on the behavior.
I need to use the TabView because it "remembers" the position of the list when you move from tab to tab. Creating my own tab control causes the list to reset everytime its displayed and I understand why. We also need to wrap the TabView under the NavigationView because our application subviews need to display their own tabs.
My questions, what am I doing wrong that is causing these inconsistencies in the navigation header. I have tried putting each list in it's own Stack but no joy, same issue keeps happening.
Any assistance would be greatly appreciated, we are currently blocked on our navigation design because of this anomaly. Hoping it's out fault so we can correct it.
----> the complete code <-------------------------------------------------------
struct ContentView: View {
#State var selectedTab: String = "Home"
#State var searchText: String = ""
var body: some View {
NavigationView {
TabView(selection: $selectedTab) {
ListView(itemTitle: "Home List", itemCount: 50)
.tabItem {
Image(systemName: "house.fill")
Text("Home")
}.tag("Home")
ListView(itemTitle: "Bookmark List", itemCount: 20)
.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Bookmark")
}.tag("Bookmark")
Text("Profile Tab")
.tabItem {
Image(systemName: "person.crop.circle")
Text("Profile")
}.tag("Profile")
}
.navigationTitle(selectedTab)
}
.searchable(text: $searchText)
.onSubmit(of: .search) {
// Do something
}
}
}
struct ListView: View {
var itemTitle: String
var itemCount: Int
var body: some View {
List(){
ForEach(1...itemCount,id: \.self){ i in
NavigationLink(destination: ListViewDetailView("\(itemTitle) \(i)")) {
VStack(alignment: .leading){
Text("\(itemTitle) \(i)").padding()
}
}
}
}
}
}
struct ListViewDetailView: View {
var text:String
init(_ text: String){
self.text = text
}
var body: some View {
Text(text).navigationTitle(Text("\(text) Detail"))
}
}

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 .......

Title for Keyboard Shortcut in 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")
}

Toolbar does not appear properly SwiftUI

In my GeneralView I have a NavigationView And a Tab View.
Inside each tabItem I navigate with some ZStack (using zIndex, hiding and showing items)
Randomly leading and trailing items are not shown properly and can't be clicked.
See below, on top of screen back button is not full. But I select same button to go on the "Coureur1View"
Info : I do not have any other problem with this navigation.
In My generalView :
.toolbar {
ToolbarItemGroup(placement: .principal) {
TitleBarView().environmentObject(objCourse)
}}
.navigationBarItems(leading: TitleBarLeadingView(),
trailing: TitleBarTrailingView())
I don't have problem with TitleBarView (principale) but with leading and trailing
In my TitleBarLeadingView :
struct TitleBarLeadingView: View {
#EnvironmentObject var objGroupe : GroupeActuel
#EnvironmentObject var objCourse : CourseActuelle
#EnvironmentObject var zindex : Zindex
var body: some View {
HStack {
if zindex.selectedTab > 0 {
if zindex.detailCoureurVisible {
Button{
zindex.detailCoureurVisible = false
} label : {
Image(systemName: "chevron.backward")
Text("Back")
}.foregroundColor(.orange)
}else{
EmptyView()
}else{
EmptyView()
}
}
}
Provided snapshots are not testable, so just idea - try to recreate navigation bar items forcefully. It can be on some known changed value (I see titled changed on gif), but also can be just by UUID():
.navigationBarItems(leading: TitleBarLeadingView().id(UUID()),
trailing: TitleBarTrailingView().id(UUID()))
Note: make .id(param) is preferable because by UUID it will be recreated by each refresh.

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.