SWIFTUI: Filtering Lists - swift

I am creating a task management app for my school project, and have hit a wall. My app is based off of the Scrumdinger Tutorial from my Apple to give context. I am currently trying to figure out how to filter items in the list based on two boolean values I have set for task completion, and being delted.
My tasks are structured as followed:
enter image description here
As you can see I've add the two different boolean variables that change based on button clicks within the detailView of the task.
The List of tasks is currently organized in this format, and I'm not exactly sure how to filter based on those value's within the List.
enter image description here
Tasks are stored here:
enter image description here
Any Help would be greatly appreciated!

You can try this simple solution and apply your own button logical, as you want
struct Object: Identifiable, Codable {
var id = UUID()
var taskDone: Bool
var text: String
}
#State var array = [Object(taskDone: true, text: "taskDone1"), Object(taskDone: false, text: "taskDone2")]
var body: some View {
List(array.filter { $0.taskDone }){ (item) in
Text("\(item.text)")
.foregroundColor(.red)
}
}

Related

Is there a way to pass functions into views as optional parameters?

I am making an application in SwiftUI that involves answering yes or no questions. Because of this I have created a subview call YesOrNoView. This subview has two buttons, one labeled yes and the other labeled no. The view accepts a binding variable of question that returns true or false based on the users answer. It also accepts a binding variable of questionsAnswered and increments it whenever either button is pressed. This behavior works for most questions in the game, but for the final question I want it to execute custom logic to take the user to a different view. To do this I am trying to make the view accept a custom action/method that I can call from either buttons action logic. Ideally, the action would also be optional so that I don't have to pass it in the 99% percent of the time when I'm not using it.
How do I pass a function as an optional parameter into a view and then run that action when a button within said view is pressed?
I tried adding actions using
struct YesOrNoView<Content: Action>: View {
...
but it couldn't find action or Action within it's scope.
I also tried using
struct YesOrNoView<Content>: View {
But I got an error saying that the Content variables type could not be inferred.
Any help would be greatly appreciated.
It return clickAction back to the view
struct GenericButton<Title: StringProtocol>: View {
let title: Title
let action: () -> Void
var body: some View {
Button(action: action) {
Text(title)
}.frame(width: 200, height: 40)
}
}
}
Usage:
GenericButton("Button") {
// Button tapped action
}
I managed to figure it out with help from #JoakimDanielson. The trick is to pass in an empty function as the default.
var myFunction: () -> Void = {}
Thanks for the help everyone.

SwiftUI on iOS 16 - Multiple selection in a List does not work

This is on iOS 16. I'm on Xcode 14.0.
I have the following view:
struct ContentView: View {
struct Ocean: Identifiable, Hashable {
let name: String
let id = UUID()
}
private var oceans = [
Ocean(name: "Pacific"),
Ocean(name: "Atlantic"),
Ocean(name: "Indian"),
Ocean(name: "Southern"),
Ocean(name: "Arctic")
]
#State private var multiSelection = Set<UUID>()
var body: some View {
NavigationView {
List(oceans, selection: $multiSelection) {
Text($0.name)
}
.navigationTitle("Oceans")
.toolbar { EditButton() }
}
Text("\(multiSelection.count) selections")
}
}
This code is taken from https://developer.apple.com/documentation/SwiftUI/List.
I am expecting to see that whenever I click on the "Edit" button, I should be able to select a few items, press "Done", then the bottom would still show the number of items I have selected. However, this is not the case:
I tried to use a debugger, and I found out that whenever I click on "Done" after selecting the items, the multiSelection resets itself to be empty. This used to work on Xcode 13. I can't really find anything on Apple's documentation regarding changes to the EditButton or changes to the List struct.
Update
I filed a bug report and Apple got back to me, they said this is expected behaviour. I guess I misinterpreted the use case for this list selection here.
Before iOS 16 the selection only worked when in editing mode. Now it works also when not editing so I believe the problem is now the selection is being cleared when done is being tapped (so it can be used when not editing).
I think we need to send feedback to request 2 selection bindings, one for editing and one for when not-editing.
i think the issue here is that your identifiers for your list are not stable. Basically every time an ocean object is created, it gets a new UUID. You dont want that UUID to change.
Any time your state property changes, the view may or may not get rebuilt, causing your oceans to get regenerated.
Try storing your oceans in like this:
#State var oceans: [Ocean] = [
Ocean(name: "Pacific"),
Ocean(name: "Atlantic"),
Ocean(name: "Indian"),
Ocean(name: "Southern"),
Ocean(name: "Arctic")
]
Alternatively, you can use the ocean's name as its identifier instead of a UUID that is generated each time its created.

Dynamically use all SwiftUI Views included in a Swift Package in an App using this package

I have been creating SwiftUI Views in a Swift Package such as this one:
public struct DottedLoaderView: View {
#State private var isOn = false
let numOfDots: Int
let dotWidth: CGFloat
let dotsColor: Color
public init(numOfDots: Int, dotWidth: CGFloat, dotsColor: Color){
self.numOfDots = numOfDots
self.dotWidth = dotWidth
self.dotsColor = dotsColor
}
public var body: some View {
HStack{
ForEach(0..<numOfDots){ i in
DottedLoaderDotView(dotWidth: dotWidth, dotsColor: dotsColor, delay: Double(i) / 2)
}
}
}
I want to make an application which will use this Package as a catalog of the Views I have implemented in the Package. I would like to display all the available Views from the package inside of a ListView in the catalog app to achieve a result similar to this:
The list row is calling the View struct from the Package. This works fine with a low amount of Views, however, I want to have hundreds of them in the future and would ideally want the app to dynamically display all the available Views and be able to iterate over them inside of a ForEach loop in the body. Is there any convenient approach that comes to mind on how to essentially "look through the Package and fetch all the Views available and have them as some variable ideally in a list?" so I can treat them as data to feed into a ForEach loop? I apologise if this is a really vague question.

How can I delete a list item in SwiftUI from a child view without using onDelete(perform:)?

I have a chat feature in my app that allows you to report and block someone from communicating with you any further.
Here's my InboxView.swift that shows a user's conversations:
List(Array(conversations.conversations.enumerated()), id: \.1.id){ (index, conversation) in
VStack{
NavigationLink(destination: ChatView(conversation_id: conversation.id, avatar: conversation.avatar, displayName: conversation.displayName, user_id: conversation.receiver_id, parentIndex: index)){
ConversationList(id : conversation.id, user_id : conversation.user_id, receiver_id : conversation.receiver_id, lastMessage : conversation.lastMessage, avatar : conversation.avatar, displayName : conversation.displayName, startedAt : conversation.startedAt)
}
Divider()
}
}
The above code simply provides the end-user an interface for them to select which conversation they want to go into. Here's where things get tricky with the following view diagram:
InboxView --> ChatView --> ProfileView
Each --> represents a NavigationLink that leads to the subsequent view. On the ProfileView.Swift page, I present a button in which the end-user can block the person they are talking to. I have already figured out how to take the user back to InboxView with a series of
#Environment(\.presentationMode) var mode
and
self.mode.wrappedValue.dismiss()
but for convenience, I also want to delete the list item that was associated with the blocked user's conversation.
How can I tell InboxView which ChatView triggered the delete request and pass that through a function like this?
func removeRow(at offsets: IndexSet){
if let first = offsets.first {
let conversationRemoving = conversations.conversations[first]
conversations.conversations.remove(at:first)
}
}
I don't see in the documentation for presentationMode to trigger a function via wrappedValue
It could be done directly inside List (as we have access to index in it) and remove record from already fetched results.
If person model would have specific field (say blocked), then it could be like below (in pseudo-code, to be shorter):
List(Array(conversations.conversations.enumerated()), id: \.1.id){ (index, conversation) in
VStack{
NavigationLink(destination: ChatView(...)) {
ConversationList(...)
}
Divider()
}
.onAppear { // called on show and on return back
if conversation.receiver.blocked { // << here !!
// better to do it asynchronously
DispatchQueue.main.async {
self.conversations.conversations.remove(at: index) // << here !!
}
}
}
}

SwiftUI macOS Scroll a List With Arrow Keys While a TextField is Active

I'd like to use a SwiftUI TextField and a SwiftUI List to render a "search box" above a list of items. Something roughly like the search box available in Safari's Help menu item...which provides a search box where you can always enter text while simultaneously browsing through the list of results using the up and down arrow keys.
I've played with onMoveCommand, focusable, and adjustments to the "parent" NSWindow, but haven't found a clear and obvious way for the TextField to constantly accept input while still being able to navigate the underlying List using the up and down arrow keys. The following code allows for either text to be entered in the TextField, or list entries to be navigated through, but not both at the same time...
struct ContentView: View {
#State var text: String = ""
#State var selection: Int? = 1
var body: some View {
VStack {
TextField("Enter text", text: $text)
List(selection: $selection) {
ForEach((1...100), id: \.self) {
Text("\($0)")
}
}
}
}
}
You can wrap a NSTextField in a NSViewRepresentable and use the NSTextFieldDelegate to intercept the move up and down keys. You can take a look into my suggestions demo.
Source of a text field with suggestions