SwiftUI: How to Disable List Scrolling on Reusable View - swift

I've got an interesting glitch in SwiftUI -
I'm using a List as part of my interface that is intended to be un-scrollable. This is how I've designed it:
List {
ForEach(myArr) {
...
}
}
.listStyle(.plain)
.onAppear {
UIScrollView.appearance().isScrollEnabled = false
UIScrollView.appearance().showsVerticalScrollIndicator = false
}
Now, when I first load the view containing this list, it works perfectly; you cannot scroll this list. However, if I navigate to a different page in my application and then back to the page containing this list (which creates a new instance of the view), scrolling becomes enabled.
How can I fix this?

Use .disabled(true) on the list to disable interaction with the list.

Related

SwiftUI -- Handling Interactions in Form

I'm creating a more complex UI that consists of several sub-parts, each sub-part is enclosed in a Section and all sections have to go in one Form
As soon as several Buttons belong to one Section, each action of the Buttons is triggered as soon as one Button is clicked.
In the following code
struct ContentView: View {
#State private var cnt = 0
var body: some View {
Form {
Section (header: Text("Counter")){
HStack {
Button("Increment"){
print("Inc")
cnt += 1
}
Spacer()
Button("Decrement"){
print("Dec")
cnt -= 1}
}
Text("Counter : \(cnt)")
}
}
}
}
a tab one one of the two Buttons triggers both actions. By adding .buttonStyle(PlainButtonStyle()) to each Button or by removing the Form everything works as expected. Unfortunately the real UI is more complex and relies on the Form widget. The .buttonStyle(PlainButtonStyle()) removes the visual hint that the elements are active.
Is there a different way to solve my problem?

Exclude button from disabling

I have custom view with different content inside including Button.
For example:
VStack {
Text("Some text")
Button() // 1-th
Button() // 2-th
.disabled(false) // This doesn't help, despite it logical
}
.disabled(true) // Line with disabling
I want 2-th button to be always enabled no matter what is set in "Line with disabling".
According to the documentation this does not work that way. It works the other way around. As you allready discovered the .disable on the parent overrides the value set in the child.
Possible solution would be to not use the parent .disable instead use it on each child element.

SwiftUI array.isEmpty do not show until received array from Firestore

Currently have a VStack populating with items from firestore and am trying to display a message if there are no items in the db. It works perfectly but there is a brief second where it shows the "No Items" message before loading the list of items. I understand it simply hasn't gotten the results yet from the db. Is there any way to hold off showing ANYTHING until that call is complete?
VStack(alignment: .leading){
if itemList.isEmpty {
Text(“No items”)
} else {
ForEach(itemList.indices, id: \.self) { i in
Text(itemList[I].item)
}
}
}
.onAppear() { self.readItems() }
No, you can hold rendering until there is data; that might take way too long. Your code has to always render something, no matter what state the app is in.
But what you can do is set a variable to signal whether data has been loaded, like isLoaded, set it initially to false, set it to true in the completion handler, and use that to determine what to show. Most commonly, you'll show a single Text view Loading... in that case, and then switch over to what you now have once isLoaded is true.

Why is my SwiftUI List row not always updating internally

I have a list of reminders grouped into sections by completion and date. With data coming from an ObservedObject DataStore called global. I pass a realmBinding to the cell. The cell can update this binding and it will trigger the data store to update.
List {
// Past Due
if self.global.pastDueReminders.count > 0 {
Section(header: SectionHeader {}){
ForEach(self.global.pastDueReminders) { reminder in
NavigationLink(destination: ReminderDetail( reminder: reminder.realmBinding())) {
GeneralReminderCell(reminder: reminder.realmBinding())
}
}
}
}
// Completed
if self.global.completeReminders.count > 0 {
// Same as PastDue but for Completed
}
}
The cell looks something like:
struct GeneralReminderCell: View {
#Binding var reminder:Reminder
var body: some View {
HStack(alignment:.top, spacing: 10) {
Image(systemName: reminder.completed ? "checkmark.circle.fill" : "circle")
.onTapGesture(perform:{ self.reminder.completed = !self.reminder.completed })
VStack(alignment: .leading, spacing: 2) {
Text("Follow up with \(reminder.client.fullName)").fontWeight(.semibold)
if reminder.title.count > 0 {
Text(reminder.title)
}
Text(reminder.date.formatted()).foregroundColor(.gray)
}
}.padding(.vertical, 10)
}
}
When tapping on an image it toggles the reminder completion state and its position changes in the List view. The image that was tapped should changed to a filled in check when completed.
This behaviour almost always happens as expected, but sometimes the checked image will get out of sync with the completed state of reminder. I've look at this for quite some time and have not made much headway. Why is the checked image not always matching the state of the data?
Even though this is a very old question, I may have been working on what appears to be this same problem. In my case, my App is running on macOS. At first, the problem also seemed to be very intermittent and had been difficult to reproduce consistently.
I also have a View with a ForEach supplying rows to a List. My row's View contains an #State for an Optional Image that gets updated several different ways via actions performed by that same row View (e.g. Continuity Camera, file picker or drag & drop). The issue is that sometimes the new Image is shown and sometimes it is not shown. Using Self._printChanges() I am able to see that the #State is changing and the row's body it is being redrawn, however, the rendered View does not change. The only pattern that I am able to observe is that this issue only seems to occur with the last row in the List. Based on the success of my workaround below, it seems to confirm that there is an issue with the way SwiftUI's List reuses table cells.
My solution/workaround is to replace:
List {
ForEach {
}
}
With:
ScrollView {
LazyVStack {
ForEach {
}
}
}

$ionicHistory.goBack() not working correctly with ion tabs

My page contains 3 tabs, if i forward to next page from tab3 and then coming back from that page using $ionicHistory.goBack() it redirects to tab1. how to make it redirects to tab3 itself
function sample($scope,$ionicHistory){
$ionicHistory.goBack();
};
}
I'm not sure it's possible to do that just through $state. I'm new to Ionic myself as I had my first peek back in December (2016). However, I happened to have this exact scenario. This is how I solved it.
In my case, I was not using tabs. Instead, I had three different DIVs that were visible depending on the "tab number". When initializing the view, I set it to "1" and used the property to control what code is executed.
I'm guessing that the control you're using has a property like that to identify and set the particular tab you need, as this is the most likely way to change tabs on tab-click. Consider putting the value of that property "tab #" into the appropriate service used by the controller. This is a stripped-down version of actual code in one of my services defined via Factory.
YourService.js:
// controllers are instances. When navigating away, state is lost. This
// tracks the tab we wish to view when we load the home page.
var activeHomePageTab = 1;
var service = {
getActiveTab: getActiveTab,
setActiveTab: setActiveTab,
...
};
return service;
////////////////
function getActiveTab() { return activeHomePageTab; }
function setActiveTab(num) { activeHomePageTab = num; }
...
Here, the functions are private. In your controller, when you set your tab, also set this property. As a singleton, this will remain in effect as long as your application is running.
Next, in your init() routine, when the controller is loaded, check for this property and set the tab if it is defined:
function init() {
var tab = YourService.getActiveTab();
if (tab !== undefined) {
setTab(tab);
} else {
setTab(1)
}
...
}
Of course, there are other ways - maybe using Value or a property on a Constant object.