Custom "list" view - swift

In SwiftUI, a List will automatically format the sub views you pass it, so something like this:
List {
Text("A")
Text("B")
}
Will result in both text views being correctly placed, with separators between them etc...
Additionally, it is also possible to mix static and dynamically generated data, like this:
List {
Text("A")
ForEach(foo) { fooElement in CustomView(fooElement) }
Text("B")
ForEach(bar) { barElement in CustomView(barElement) }
}
My goal is to write my own custom type that would allow this kind of use by its users (ie: a view that lets the users provide views using the new function builder DSL, without requiring them to write their own modifiers to place them on the screen), but I don't know at all what to put in the initialiser of my custom view.
The native SwiftUI views are making use of #ViewBuilder and receive a generic type conforming to View, but the way they are able to extract elements (from, say, a ForEach view) is mysterious and I'm pretty sure it's not even possible.
Maybe I missed something, so I'm curious to know your opinion about this?
EDIT:
An example might be clearer. Many of you must have seen the nice examples online with cards arranged on top of each other using a ZSack, so what if I want to create a CardStack type that would allow my users to write code like this?
CardStack {
SomeView()
AnotherView()
ForEach(1...10) { i in
NumberView(i)
}
}
This would result in 12 cards stacked on top of each other, note that the types are not homogenous and that we used ForEach.

The first part of your challenge is figuring out ViewBuilder. As for the flow of data from children to ancestors, please read on:
By using preferences (check my article https://swiftui-lab.com/communicating-with-the-view-tree-part-1/), you can make information flow from children to ancestors.
Once you fully understand preferences and get them to work, you can try making the implementation more clean and transparent, so that the end user of your Stack doesn't even realize he is using preferences. To do so, you could use some View extensions. The final code could look something like this:
CardStack {
SomeView().card(title: "", border: .red)
AnotherView().card(title: "", border: .green)
ForEach(items) { item in
NumberView(item).card(item.name, border: .blue)
}
}
And your .card() implementation may be something like this:
extension View {
func card(title: String, border: Color) -> CardCustomizer<Self> {
return CardCustomizer(viewContent: self, title: title, border: border)
}
}
struct CardCustomizer<Content: View>: View {
public let viewContent: Content
public let title: String
public let border: Color
var body: some View {
viewContent.preference(MyCardPref.self, value: ...)
}
}
I think it is best if you try first to make it work without the View extensions, so you get one thing right. Then proceed to encapsulate the preference logic with extensions. Otherwise there are too many new things to deal with.

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.

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.

Ambiguous reference with a Picker in Swift UI

I'm trying to make a Picker with SwiftUI. I've follow a tutorial but don't have the same result. There is Ambiguous reference on the self.category.count and self.category[$0]. After one entire day, I still don't know how to fix it ...
import SwiftUI
struct Picker : View {
var category = ["Aucun", "BF Glaive", "Baguette", "Negatron", "Larme", "Ceinture", "Arc", "Cotte", "Spatule"]
#State private var selectedCategory = 0
var body: some View {
VStack {
Picker(selection: $selectedCategory, label: Text("Item")) {
ForEach(0 ..< self.category.count) {
Text(self.category[$0])
.tag($0)
}
}
Text("Selected : \(category[selectedCategory])")
}
}
}
To resolve name conflicts between modules, you can either:
Rename your Picker to something else.
Use the qualified (full) name:
SwiftUI.Picker(selection: $selectedCategory, label: Text("Item")) {
The error message Ambiguous reference to member 'count’ is misleading. What you have is a naming conflict between SwiftUI.Picker and your Picker struct. Just change the name of your struct to something other than Picker. For example:
struct CategoryPicker : View {
// ...
}
Alternatively, you can resolve the naming conflict between the modules by providing the fully qualified name for SwiftUI.Picker (as Sulthan pointed out):
SwiftUI.Picker(selection: $selectedCategory, label: Text("Item")) {
// ...
}
However, I wouldn’t advise this option unless your intention is to replace SwiftUI.Picker everywhere in your app. Your code includes the category array and a Text view, so it's unlikely this is what you're after.
If the app eventually needs OtherPicker with a SwiftUI.Picker and the module name is omitted again, it’ll be even more confusing to track down the error—and you’ve already spent an “entire day” on it. So, best to avoid this possibility by not introducing the conflict at all :)

Swift best practice: How to keep track of different objects?

I'm wondering what is a good solution to keep track of different objects of same type.
I have this function:
private extension MenuButtonsViewController {
// TODO: Find a way to find correct button based on MenuItem
func buttonFor(for menuItem: MenuItem) -> EmojiButton? {
guard let subViews = stackView.subviews as? [EmojiButton] else {
return nil
}
let button = buttonFactory.makeEmojiButton(title: menuItem.icon)
for subView in subViews where subView == button {
return subView
}
return nil
}
}
I have an array (UIStackView) with a varying number of buttons (EmojiButton). The buttons are created with content from MenuItem.
I'm looking for a good and clean solution how to find and remove a particular button from the stackView array, based on a MenuItem.
So far I had three ideas:
To create a new object, initalized with same values as the one to remove, and then match using ==. (Solution above). This didn't work.
To add an id to all buttons, and then a corresponding id to the MenuItem object. But this doesn't seem like an elegant solution to have to add that everywhere, and expose this variable from the button object.
Maybe store the button in a wrapper class (like MenuItemButton) with an id to match to, or by storing the MenuItem object so I can match against that.
Any ideas? How is this usually done?
If MenuItem and EmojiButton inherit from UIView, you can make use of the tag property that is available on all UIView's.
You first need to assign a unique tag value to each of your MenuItem's.
You then need to assign this same value to the corresponding Emoji button's tag property. (This would be a good thing to do in your factory.)
Having done that, you can modify your function as follows:
//assumes MenuItem and EmojiButton inherit from UIView
func buttonFor(for menuItem: MenuItem) -> EmojiButton? {
return stackView.viewWithTag(menuItem.tag) as? EmojiButton
}

Using "ViewState" in RxSwift/MVVM

This question is very broad but I'm not sure which aspect of it I should focus on. I have a goal to abstract away the recurring patterns of my screens such as errors, loading, empty data. The views that represent these states will not change much between the many screens I have. Perhaps they could be parameterized to allow that flexibility (e.g. showError(message: "404")).
I liked this article as a method of encapsulating the reusable UI aspects of this.
But it appears to work in an imperative context. So I have an API call and I can showError and in the response I can hideError. Thats all fine.
Now I use an RxSwift/MVVM approach where each screen binds to inputs and outputs. And I like to simplify the state my screen knows about by using a "View State" concept.
As you can see in this snippet, I can reduce a lot of logic a single Observable that the view renders.
let getFoos: (String) -> Observable<FooViewStateState> = { query in
fooService.perform(query)
.map { results in
if results.isEmpty {
return ViewState.noResults(query: query)
} else {
return ViewState.matches(query: query, results: results.map { $0.name })
}
}
.startWith(ViewState.searching(query))
}
The problem is that by using an enum ViewState its now unclear to me how to use the imperative API from before "showLoading / hideLoading ... showError / hideError, etc..." when I'm switching on the cases of this enum. If the ViewState Observable emits .loading I'd have to hide the error screen, hide the empty screen, etc..