Keeping it Swifty

When to pass data to a View?

I'm writing some code and want to keep my source Swifty - which I define to mean: Keeping the View all about the declaritive layout of the components. Keeping the State info about the View inside the view and managed by the system. Allowing the Doman Model to be accessed via the Property Wrappers. There is a lot to writing good Swifty code.

I ran into a problem with a List view that had the typical embedded RowView and then on each row I wanted a Button with the included action code. The List was inside a parent View that held the ViewModel - a list of Items that the user could choose from. When the user selected an item they could click the "+" button to add it to their personal list. This List was searchable. Of course I was using the newer .searchable() modifer for the list. When embedded in a NavigationView would display a Search TextField. Using this search criteria I could have the list filter the Items to what the user desired. After adding an Item to their list - the list should reset (clear the search term).

The problem I encountered was that I seemed to need to pass the whole Item list viewModel into the Row and then into the Button. This was just to heavy to be good and Swifty! I must be doing it WRONG.

So off the the intertubes I went ... looking for an example of using a Button on a Row of a List. Do you know that is very hard to search for... took hours and many attempts. I didn't find a solution in my favorite places (Stweart Lynch or Hacking w/ Swift). I found a new and great site: Cocoacasts. And this article: Building Dynamic Lists in SwiftUI

The Ultimate Guide to SwiftUI List Views - Part 2

Now this article didn't resolve my confussion. But I got in touch with the author via Twitter and he responded, asking me to give him some example code to better understand my questions. So I took his example from the article and added a bit to show where I was going and what I wanted. He obliged and sent me a pull-request on my Repo, with his solution to my problem. I have to admit ... this is the first time someone has used the coding tools such as Github to resolve my coding problems. It made me fell like a grown up, responsible adult coder. Thanks Peter!

If you take a look at Peter's example over there. You see that the Row View gets one Book, not the whole list or not even the viewModel. So the problem I'm seeing is that the Button that adds the book to the user's personal list needs to clear and reset the search criteria - so that the complete list is displayed - after adding the selected book.

Peter had three solution options for me. The Swiftyest option was a new technique that I had not even dreamed of - and worked quite well. He used the swipeActions to add the button. But because this is done at the List hierarchy level - the Button has access to the viewModel. And that resolves my problem.


struct SearchableBooksListView: View {

// needs a NavView for Search field to show up

@StateObject var viewModel = SearchableBooksViewModel()

var body: some View {

List(viewModel.books) { book in

SearchableBookRowView(book: book)

.swipeActions {

Button {

viewModel.addToReadingList(book)

viewModel.searchTerm = ""

} label: {

Image(systemName: "plus")

}

}

}

.searchable(text: $viewModel.searchTerm)

}

}


Now this has pros/cons but it does look really nice in the code. I don't like the UI as well because the Swipe action is a hidden affordance of the List. Where as a typical Button would be overt and always present. Would a swipe action icon on the row be an acceptable indicator of the affordance?

As with all design problems... it's the details that one rules out that define the solutions.

If you want to see Peter's excellent code - here's
SearchListExample.