SwiftUI abstract view for an array of different types of objects - swift

I want to make a list of two types of objects (MotorBikeRow and WithFootBikeRow will be these two slightly different row views of this list)
I am coming from Java and naturally I will create an abstract class called Bike. Such that I have a list of two (or more of course) types of objects :
#Binding var bikeList:[Bike]
But the problem is how to do this "abstract class" in Swift?
I am currently using a protocol called Bike that the two structs MotorBike and WithFootBike conforms to.
List {
ForEach(...) i in {
if bikeList[i] is MotorBike {
MotorBikeRow(motorBike: $bikeList[i]) << error
} else {
WithFootBikeRow(withFootBike: $bikeList[i]) << error
}
}
}
But we immediately see a problem further, if these two views ask a binding.
#Binding var motorBike:MotorBike
#Binding var withFootBike:WithFootBike
So the question
How to cast the binding from Bike to MorotBike/WithFootBike?
Of course I did used this "trick" : How can I cast an #Binding in Swift but there is memory leaks!
Is it the correct SwiftUI/Swift approach? Maybe I am completely wrong and too much influenced by "Java" that I do not see a more elegant solution?

Related

SwiftUI: How could I use polymorphism to make a model class create its own View

I am trying to build a small App in which you can create tests with questions using SwifUI, but finding how to make everything works out is getting hard for a newbie like me. The app would show a list of questions in a main scrollable view and these questions could be of different types such as true or false, text, multiple choice, etc… and could be active or not.
I thought it would be great that all different types of questions adopted the same protocol. This protocol would also define a function or a computed property in charge of display its on view using the values store in the different attributes. However, the problem comes up when trying to modify any of this parameters interacting with that View. Let's say I want to add a toggle button that active or reactive the question, modifying one of the values of that question. With the different solutions I implemented, I didn't get the view being rebuild/updated.
I tried several things to accomplish this, like wrapping those properties that are supposed to update their values with #State or #Binding. I also tried to turn those properties into ObservableObjects, adding new classes that adopt the ObservableObject protocol, but it does not work. The only thing that seems to work is, for any type of Question, create a view, with an observable ViewModel. Later, in the view where I display all the question, I have to create a Switch with all the different possibilities.
What I don't like about this solutions is that if I wanted to add a new type of question, I would have to modify this main view to include an extra case for this new type of question, what is against the Open-Closed principle.
Do you have any suggestion guys to assign this responsibility to any question class instead of to the main view?
Thank you in advance :)
Usually you don't want a model to know about the view. That's backwards. But yet we often want to hide the concrete selection logic behind a single call that does different things depending on the data provided (similar to polymorphism via function overloading).
The key idea is the use of the Factory Pattern combined with the Visitor Pattern to keep in harmony with the Open-Closed Principle.
When we do this kind of thing with regular objects, we often use a factory method to return the proper subclass for the input data. Inside that factory method usually sits a switch statement. The factory interface lets us honor the Open-Closed principle so that ContentView doesn't change when we add new question types. Chances are, what follows is probably very similar to what you had yourself with the view model approach.
In SwiftUI, the best approach at a factory-style view would be to create a QuestionView that then knows how to create the correct concrete view for each question object. I hope that default + fatalError() makes you consider how an enum might be useful here.
struct QuestionView: View {
let question: Question
var body: some View {
switch question {
case let q as TextQuestion:
TextQuestionView(question: q)
case let q as DoubleQuestion:
DoubleQuestionView(question: q)
default:
fatalError("Unknown question type")
}
}
}
Then in your main view it would be polymorphic, reacting dynamically to the actual Question instances. You'd use it something like this:
struct ContentView: View {
#State private(set) var questions: [Question]
var body: some View {
NavigationView {
Form {
ForEach(questions, id: \.key) { question in
QuestionView(question: question)
}
Button("Submit") {
let answers = Answers()
for question in questions {
question.record(answers: answers)
}
print(answers)
}
}
.navigationTitle("Questions")
}
}
}
A reasonable way to extract the answers is to use the Visitor Pattern in a way similar in structure to Encodable's encode(to encoder: Encoder). We expect each specialty view to communicate with its specific Question object, and then expect the Question object to contain an implementation of func record(answers: Answers). When it's time, loop through questions and tell them to record their answers. (Note that we can add various Answers implementations without changing the Question subclasses, in keeping with the Open-Closed principle).
The Question objects are like view models, and they are ObservableObjects. You can see how they record their answers when asked.
For this to work, they cannot be protocols with associated types. That just kills using them in an array.
class TextQuestion: Question, ObservableObject {
#Published var answer = ""
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
class MeasurementQuestion: Question, ObservableObject {
let unit: String
#Published var answer = 0.0
init(key: String, question: String, unit: String) {
self.unit = unit
super.init(key: key, question: question)
}
override func record(answers: Answers) {
answers.addAnswer(key: key, value: answer)
}
}
Then each individual question subtype view will watch its own Question instance:
struct TextQuestionView: View {
#ObservedObject private(set) var question: TextQuestion
var body: some View {
Section(question.question) {
TextField("Answer", text: $question.answer)
}
}
}
struct MeasurementQuestionView: View {
#ObservedObject private(set) var question: MeasurementQuestion
var body: some View {
Section(question.question) {
HStack {
TextField("Answer", value: $question.answer, format: .number)
Text(question.unit)
}
}
}
}
You can simply add your list of questions to the preview and see how it works:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(questions: Example.questions)
}
}
struct Example {
static let questions: [Question] = [
TextQuestion(
key: "name",
question: "What...is your name?"
),
TextQuestion(
key: "quest",
question: "What...is your quest?"
),
[
TextQuestion(
key: "assyria",
question: "What...is the capital of Assyria?"
),
TextQuestion(
key: "color",
question: "What...is your favorite colour?"
),
MeasurementQuestion(
key: "swallow",
question: "What is the air-speed velocity of an unladen swallow?",
unit: "kph"
)
].randomElement()!
]
}
I'm not sure I like this implementation for anything beyond a toy project—I prefer stronger layer separations. This does, however, setup a polymorphic view that adjusts to data implementation types. The key idea is the use of the Factory Pattern combined with the Visitor Pattern.

How can I know the source object of dot with no prefix, is it SwiftUI or is it Swift?

So, basic question probably but I would like to understand it clearly.
I don't understand the dot something syntax with no prefix. For example the navigationViewStyle below, I can guess that it is a method of the NavigationView which is a struct.
We didn't declare a Navigation View Struct because it's SwiftUI and that is style in this programmatic UI?
In that case when we write dot something how does it know which object to relate to? am I referencing the struct definition or a this instance of the struct?
If I write Self. or self. none of the methods show up.
Also for the .stack, I just picked .stack. When I checked the navigationViewStyle documentation, I did not find the three options (stack, automatic, columns) which appear when coding. What is the origin of the three options? Is it inherited from a higher class or a combintation of multiple protocols? In that case the logic scales up and down, how do you plan out what to do?
My problem is that when I read an already written code it seems organic but my issue is if I want to write code from scratch then unless I memorize the pattern I have no idea how to reach the same decision on what to write.
Also is this just for SwiftUI or in Swift in general?
import SwiftUI
#main
struct MyApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
SymbolGrid()
}
.navigationViewStyle(.stack)
}
}
}
To complete #hallo answer, when you have a method that have a parameter of some type, then you can use any static var that are define in the type without needing to specify the type.
struct MyType {
var myVar: Int
static var one = MyType(myVar:1)
static var two = MyType(myVar:2)
}
// suppose you have somewhere a func defined as :
func doSomething(with aVar: MyType {
// do something
…
}
// you can call the function :
doSomethin(with: MyType.one)
// or
doSomething(with: .one)
In the second call the compiler know that the type of data must be MyType so it will deduce that .one is MyType.one, so you do not have to specify MyType. Just use « .one ». This is some syntaxic sugar from Swift.
This also work for enum where you do not have to specify the type in such case.
.navigationViewStyle() takes anything conforming to NavigationViewStyle and the available styles are also defined as static computed variables for convenience. You can also write .navigationViewStyle(StackNavigationViewStyle()) for the same result, but that seems much less readable in my opinion and therefore you just write .navigationViewStyle(.stack)
You can always just ⌘ (command) + click on anything and then "Jump to Definition" to see (or ⌃⌘ + click) to see the underlying definition
#available(iOS 13.0, tvOS 13.0, watchOS 7.0, *)
#available(macOS, unavailable)
extension NavigationViewStyle where Self == StackNavigationViewStyle {
/// A navigation view style represented by a view stack that only shows a
/// single top view at a time.
public static var stack: StackNavigationViewStyle { get }
}
As you see NavigationViewStyle.stack will return a StackNavigationViewStyle

Do I have to use an ObservableObject in SwiftUI?

I would like to use a struct instead of a class as a state for my View, and as you may know, ObservableObject is a protocol only classes can conform to.
Do I have to wrap my struct in a ViewModel or some other similar type of object ? What happens if I don't ?
A sample on what that looks like now :
import Foundation
import SwiftUI
struct Object3D {
var x : Int
var y : Int
var z : Int
var visible : Bool
}
struct NumberView<Number : Strideable> : View {
var label : String
#State var number : Number
var body : some View {
HStack {
TextField(
self.label,
value: self.$number,
formatter: NumberFormatter()
)
Stepper("", value: self.$number)
.labelsHidden()
}
}
}
struct ObjectInspector : View {
#State var object : Object3D
var body : some View {
VStack {
Form {
Toggle("Visible", isOn: $object.visible)
}
Divider()
Form {
HStack {
NumberView(label: "X:", number: object.x)
NumberView(label: "Y:", number: object.y)
NumberView(label: "Z:", number: object.z)
}
}
}
.padding()
}
}
struct ObjectInspector_Previews: PreviewProvider {
static var previews: some View {
ObjectInspector(object: Object3D(x: 0, y: 0, z: 0, visible: true))
}
}
You don't have to use #ObservedObject to ensure that updates to your model object are updating your view.
If you want to use a struct as your model object, you can use #State and your view will be updated correctly whenever your #State struct is updated.
There are lots of different property wrappers that you can use to update your SwiftUI views whenever your model object is updated. You can use both value and reference types as your model objects, however, depending on your choice, you have to use different property wrappers.
#State can only be used on value types and #State properties can only be updated from inside the view itself (hence they must be private).
#ObservedObject (and all other ...Object property wrappers, such as #EnvironmentObject and #StateObject) can only be used with classes that conform to ObservableObject. If you want to be able to update your model objects from both inside and outside your view, you must use an ObservableObject conformant type with the appropriate property wrapper, you cannot use #State in this case.
So think about what sources your model objects can be updated from (only from user input captured directly inside your View or from outside the view as well - such as from other views or from the network), whether you need value or reference semantics and make the appropriate choice for your data model accordingly.
For more information on the differences between #ObservedObject and #StateObject, see What is the difference between ObservedObject and StateObject in SwiftUI.
I would like to use a struct instead of a class as a state for my View, and as you may know, ObservableObject is a protocol only classes can conform to.
A model is usually shared among whichever parts of the app need it, so that they're all looking at the same data all the time. For that, you want a reference type (i.e. a class), so that everybody shares a single instance of the model. If you use a value type (i.e. a struct), your model will be copied each time you assign it to something. To make that work, you'd need to copy the updated info back to wherever it belongs whenever you finish updating it, and then arrange for every other part of the app that might use it to get an updated copy. It's usually a whole lot easier and safer to share one instance than to manage that sort of updating.
Do I have to wrap my struct in a ViewModel or some other similar type of object ? What happens if I don't ?
It's your code -- you can do whatever you like. ObservableObject provides a nice mechanism for communicating the fact that your model has changed to other parts of your program. It's not the only possible way to do that, but it's the way that SwiftUI does it, so if you go another route you're going to lose out on a lot of support that's built into SwiftUI.
The View is a struct already, it cannot be a class. It holds the data that SwiftUI diffs to update the actual UIViews and NSViews on screen. It uses the #State and #Binding property wrappers to make it behave like a class, i.e. so when the hierarchy of View structs is recreated they are given back their property values from last time. You can refactor groups of vars into their own testable struct and include mutating funcs.
You usually only need an ObservableObject if you are using Combine, it's part of the Combine framework.
I recommend watching Data Flow through SwiftUI WWDC 2019 for more detail.

How to bind an array and List if the array is a member of ObservableObject?

I want to create MyViewModel which gets data from network and then updates the arrray of results. MyView should subscribe to the $model.results and show List filled with the results.
Unfortunately I get an error about "Type of expression is ambiguous without more context".
How to properly use ForEach for this case?
import SwiftUI
import Combine
class MyViewModel: ObservableObject {
#Published var results: [String] = []
init() {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.results = ["Hello", "World", "!!!"]
}
}
}
struct MyView: View {
#ObservedObject var model: MyViewModel
var body: some View {
VStack {
List {
ForEach($model.results) { text in
Text(text)
// ^--- Type of expression is ambiguous without more context
}
}
}
}
}
struct MyView_Previews: PreviewProvider {
static var previews: some View {
MyView(model: MyViewModel())
}
}
P.S. If I replace the model with #State var results: [String] all works fine, but I need have separate class MyViewModel: ObservableObject for my purposes
The fix
Change your ForEach block to
ForEach(model.results, id: \.self) { text in
Text(text)
}
Explanation
SwiftUI's error messages aren't doing you any favors here. The real error message (which you will see if you change Text(text) to Text(text as String) and remove the $ before model.results), is "Generic parameter 'ID' could not be inferred".
In other words, to use ForEach, the elements that you are iterating over need to be uniquely identified in one of two ways.
If the element is a struct or class, you can make it conform to the Identifiable protocol by adding a property var id: Hashable. You don't need the id parameter in this case.
The other option is to specifically tell ForEach what to use as a unique identifier using the id parameter. Update: It is up to you to guarentee that your collection does not have duplicate elements. If two elements have the same ID, any change made to one view (like an offset) will happen to both views.
In this case, we chose option 2 and told ForEach to use the String element itself as the identifier (\.self). We can do this since String conforms to the Hashable protocol.
What about the $?
Most views in SwiftUI only take your app's state and lay out their appearance based on it. In this example, the Text views simply take the information stored in the model and display it. But some views need to be able to reach back and modify your app's state in response to the user:
A Toggle needs to update a Bool value in response to a switch
A Slider needs to update a Double value in response to a slide
A TextField needs to update a String value in response to typing
The way we identify that there should be this two-way communication between app state and a view is by using a Binding<SomeType>. So a Toggle requires you to pass it a Binding<Bool>, a Slider requires a Binding<Double>, and a TextField requires a Binding<String>.
This is where the #State property wrapper (or #Published inside of an #ObservedObject) come in. That property wrapper "wraps" the value it contains in a Binding (along with some other stuff to guarantee SwiftUI knows to update the views when the value changes). If we need to get the value, we can simply refer to myVariable, but if we need the binding, we can use the shorthand $myVariable.
So, in this case, your original code contained ForEach($model.results). In other words, you were telling the compiler, "Iterate over this Binding<[String]>", but Binding is not a collection you can iterate over. Removing the $ says, "Iterate over this [String]," and Array is a collection you can iterate over.

Swift Protocols causing Invalid Redeclaration and cluttering function table

TLDR: Using many Swift protocols in a large project is great for testing and SOLID coding, but I’m getting function clutter and invalid redeclaration clashes. What’s the best practice to avoid these problems in Swift while making heavy use of protocols?
Concretely, I want to use protocols to separate responsibilities from view classes such that they don’t need to know anything about the data models used to “decorate” them. But this is creating a lot of functions for my data model classes that are exposed throughout the app, and that are starting to clash with other protocols.
As an example, let’s say I want to set up my custom tableview cell from a certain data model in my project. Let’s call it MyDataModel. I create a decorating protocol like so:
protocol MyCellDecorator {
var headingText: String?
var descriptionText: String?
}
And then my cell is like
class MyCell: UITableViewCell {
#IBOutlet weak var headingLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
func setup(fromDecorator decorator: MyCellDecorator) {
headingLabel.text = decorator.headingText
descriptionLabel.text = decorator.descriptionText
}
}
Now all I need to do is provide an extension from my data model class implementing MyCellDecorator, providing headingText and descriptionText, and I can plug my data model object into setup(fromDecorator:).
extension MyDataClass: MyCellDecorator {
var headingText: String {
return “Some Heading“
}
var descriptionText: String {
return “Some Description“
}
}
This makes the cell easy to test; it clearly separates responsibilities, MyCell and the UIViewController driving it now need to know nothing about MyDataModel..
BUT now MyDataModel has two extra properties, headingText, and descriptionText - available everywhere. But MyDataModel already extends 10 other decorator protocols for specific UI throughout my project, and it so happens that another protocol already defines headingText, so I get the compilation error “invalid redeclaration of ‘headingText’”.
With all of this headache, I decide to quit, go ahead and just pass MyDataModel into MyCell, it all compiles but I lose all the aforementioned advantages.
What are good ways, in such a big project as this, to score those sweet sweet protocol wins, without cluttering up my class’s function tables and having redeclaration clashes between different extensions?
I agree with where Andrey is going, but I believe it's even simpler. You just need decorator types, and the way you've described them, they should be able to be simple structs, with no inherent need for protocols.
struct MyCellDecorator {
let headingText: String
let descriptionText: String
}
(I've made these non-optional, and I strongly recommend that unless you have a UI distinction between "empty string" and "none.")
Extensions work almost exactly as you've done before:
extension MyDataClass {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: "Some Heading",
description: "Some Description")
}
}
In some cases, you may find that model objects have very consistent ways that they generate a decorator. That's a place where protocols will allow you to extract code such as:
protocol MyCellDecoratorConvertible {
var headingText: String { get }
var descriptionText: String { get }
}
extension MyCellDecoratorConvertible {
func makeMyCellDecorator() -> MyCellDecorator {
return MyCellDecorator(headingText: headingText,
description: description)
}
}
This example captures the case where the cell happens to have exactly the right names already. Then you just have to add MyCellDecoratorConvertible and the property comes for free.
The key point to all of this is that rather than have model.headingText you'll have model.makeMyCellDecorator().headingText, which will address your explosion of properties.
Note this will generate a new Decorator every time you access it, which is why I'm using a make (factory) naming convention. There are other approaches you might consider, such as an AnyMyCellDecorator type eraser (but I'd start simple; these are likely very small types and copying them is not expensive).
You can split the UI into modules and use internal extensions. Those will not appear in other modules, which will prevent myCellDecorator from showing up everywhere. If more convenient, you can put the myCellDecorator extensions in the same file with MyCell and mark them private.
Since this is a large, existing code-base, I highly recommend allowing any existing code duplication to drive your design. There is no one pattern that is ideal for all systems. It's not even necessary to have every decorator follow the exact same pattern (in some cases it may make more sense to use a protocol; in others a struct; in others you might want both). You can create a pattern "language" without boxing yourself into a world where you're creating extra protocols just because "that's the pattern."
But MyDataModel already extends 10 other decorator protocols for specific UI throughout my project, and it so happens that another protocol already defines headingText, so I get the compilation error “invalid redeclaration of ‘headingText’”.
I think this is the main pitfall here, that you use single model to provide data for different parts of the application. If we are talking about the MVC pattern, then the single model should only provide data for corresponding controller. I think in this case there will be much less protocol adoptions in the model.
On other hand you can try to split functionality inside of the model:
For instance, if we have
protocol CellDecorator {
var headingText: String?
var descriptionText: String?
init(withSomeData data: ...) {}
}
we could create something like this
class MyCellDecorator: CellDecorator {
var headingText: String?
var descriptionText: String?
}
class MyDataClass {
lazy var cellDecorator: CellDecorator = {
return CellDecorator(withSomeData: ...)
}
}
One struct-based way I've played with is this:
Instead of extending MyDataClass, I create a simple struct (which can be fileprivate to the view controller class, or not) that looks like:
struct MyDataClassCellDecorator: MyCellDecorator {
var headingText: String? {
return "Some heading with \(data.someText)"
}
var descriptionText: String? {
return data.someOtherText
}
let data: MyDataClass
}
This way MyCell can still use the protocol to decorate itself, MyDataClass doesn't need any extension at all, and in whatever access scope I want it I get a struct that does the decorating logic for MyDataClass + MyCellDecorator.