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

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.

Related

SWIFTUI: Filtering Lists

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

How can I prevent SwiftUI from reinitializing my wrapped property just like it does with #StateObject?

From what I've read whenever you instantiate an object yourself in your view, you should use #StateObject instead of #ObservedObject. Because apparently if you use #ObservedObject, SwiftUI might decide in any moment to throw it away and recreate it again later, which could be expensive for some objects. But if you use #StateObject instead then apparently SwiftUI knows not to throw it away.
Am I understanding that correctly?
My question is, how does #StateObject communicate that to SwiftUI? The reason I'm asking is because I've made my own propertyWrapper which connects a view's property to a firebase firestore collection and then starts listening for live snapshots. Here's an example of what that looks like:
struct Room: Model {
#DocumentID
var id: DocumentReference? = nil
var title: String
var description: String
static let collectionPath: String = "rooms"
}
struct MacOSView: View {
#Collection({ $0.order(by: "title") })
private var rooms: [Room]
var body: some View {
NavigationView {
List(rooms) { room in
NavigationLink(
destination: Lazy(ChatRoom(room))
) {
Text(room.title)
}
}
}
}
}
The closure inside #Collection is optional, but can be used to build a more precise query for the collection.
Now this works very nicely. The code is expressive and I get nice live-updating data. You can see that when the user would click on a room title, the navigation view would navigate to that chat room. The chatroom is a view which shows all the messages in that room. Here's a simplified view of that code:
struct ChatRoom: View {
#Collection(wait: true)
private var messages: [Message]
// I'm using (wait: true) to say "don't open a connection just yet,
// because I need to give you more information that I can't give you yet"
// And that's because I need to give #Collection a query
// based on the room id, which I can only do in the initializer.
init(_ room: Room) {
_messages = Collection { query in
query
.whereField("room", isEqualTo: room.id!)
.order(by: "created")
}
}
var body: some View {
List(messages) { message in
MessageBubble(message: message)
}
}
}
But what I've noticed, is that SwiftUI initializes a new messages-collection every single time the user interacts with the UI in any way. Like even when an input box is clicked to give it focus. This is a huge memory leak. I don't understand why this happens, but is there a way to tell SwiftUI to only initialize #Collection once, just like it does with #StateObject?
Use #StateObject when you want to define a new source-of-truth reference type owned by that View and tied to its life-cycle. The object will be created just before the body is run for the first time and is stored in a special place by SwiftUI. If the View struct is recreated (e.g. by a parent View's body recompute by a state change) then the previous object will be set on the property instead of a new object. If the View is no longer init during body updates then the object will be deinit.
When you pass a #StateObject into a child View then in that child View uses #ObservedObject to enable the body of the child view to be recomputed when the object changes, just the same way the body of the parent View will also be recomputed because it used an #StateObject. If you use #ObservedObject for an object that was not an #StateObject in a parent View then since no View is owning it then it will be lost every time the View is init during body updates. Also, any objects that you create in the View init, like your Collection, those are immediately lost too. These excessive heap allocations could cause a leak and slows down SwiftUI. Objects the View owns must be wrapped in #StateObject to avoid this.
Lastly, don't use #StateObject for view state and certainly don't try and implement a legacy "View Model" pattern with them. We have #State and #Binding for that. #StateObject is only for model objects (as in your domain types: Book, Person etc.), loaders or fetchers.
WWDC 2020 Data Essentials in SwiftUI explains all this very nicely.

Pass image name to custom Image subclass in SwiftUI

I am trying to accomplish something that looks pretty simple: I have a custom Image subclass in SwiftUI which I’m using for convenience, and I would like to use it in my app. My end goal is to use the OnboardingThumbnailImage in other views and simply pass an image name to the constructor.
import SwiftUI
struct OnboardingThumbnailImage: View {
var imageName: String
var body: some View {
Image(imageName)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
.foregroundColor(Colors.tintColor)
}
}
struct OnboardingThumbnailImage_Previews: PreviewProvider {
static var previews: some View {
OnboardingThumbnailImage(imageName: "?????")
}
}
How can I accomplish this? The compiler requires me to specify a value inside OnboardingThumbnailImage_Previews so I have no clue. I have looked into Bindings but I don't need a 'two-way street' between the views, so I'm not sure.
Can I instead just perhaps leave Image() with no arguments inside, in order to inherit the default Image constructor? If I leave Image() I get an error: Cannot invoke initializer for type 'Image' with no arguments.
This is SwiftUI's way of asking you
What image do you want to show when this View is previewed?
You can only preview SwiftUI views on macOS Catalina, so if you are not using Catalina (like me), then this feature is not very relevant to you.
You are supposed to put the image that you want to see in the previews in the ???? bit. If you are not using Catalina, or you just don't want to preview it, you can just delete the whole OnboardingThumbnailImage_Previews struct.
Also note that you can't "subclass" another view in SwiftUI. All you can do is composition, which is what you have done here. SwiftUI's design favours composition over inheritance. You can find explanations of this concept in these pages: 1, 2.
In your struct view, the variable imageName needs value for initialization. as it should be string, because you define a string variable and use it in your Image. To ignore the error you can set an empty value to your variable to ignore the requirement to init in view construction
var imageName: String = ""

Custom "list" view

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.

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