How can I know the source object of dot with no prefix, is it SwiftUI or is it Swift? - 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

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.

SwiftUI abstract view for an array of different types of objects

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?

How can i add a custom View modifier for Shapes

I want to call my ImageView with some kind of modifier to make the Image in it round or square. The code should look like:
ImageView(withURL: newsItem.imageUrl).test(Circle())
To get this behaviour i read you can extend the view like i did below. It works perfectly fine with a String but i don't understand what's different with a Shape?
I get the error Message Protocol type 'Shape' cannot conform to 'Shape' because only concrete types can conform to protocols but i don't understand that either :/. Can someone explain me the Problem and what i can do about it?
struct ImageView: View {
var clipShape: Shape
var body: some View {
ZStack {
Image("swift")
.resizable()
.aspectRatio(contentMode: .fill)
.clipShape(self.clipShape)
.shadow(radius: 6)
}
}
}
extension ImageView {
func test(_ shape: Shape) -> Self {
var copy = self
copy.clipShape = shape
return copy
}
}
btw this Code is shortened to only show the Code for the specific Problem, the View contains more than that. Just so you don't question the use of it.
Thanks in adnvance!
EDIT:
For why shapes don't work and what to do look at: https://forums.swift.org/t/how-to-make-different-clipshape/27448/2
In my Case if found out simply clipShaping the whole ImageView does the job just fine.
I also wanna link this Creating custom modifiers for Swift UI views where you can read about custom modifiers
I found this question while looking for the same title, but different intention - I want to create a modifier that can be applies specifically to something conforming to Shape so that I can do Shape like effects with it - stroke, etc.
But while I haven't found my answer yet, I think the answer to yours is that ViewModifier doesn't work to insert/change internal values of a view - instead you should be considering a ViewBuilder.
There aren't many articles that I've found really highlighting how to and when to effectively choose to use ViewBuilder, but this one from Majid entitled The power of #ViewBuilder in SwiftUI does a pretty good job.
Apple's docs on ViewBuilder also (fortunately) include a bit of sample code in the reference (one of the few places for SwiftUI) that at least show you some basic ideas of how you can use it.
The basic gist is that ViewModifier always works in general with methods on View, where ViewBuilder is used as a means of composing views together, which sounds much more like what you want. In the end, they overlap significantly in what they can do - differing primarily in how they achieve it.
Its a 3 step process:
Make a ViewModifier
Extend View with a function to apply your modifier and return the modified view
Call your modifying function on an actual View
struct RedBackGroundModifier: ViewModifier {
func body(content: Content) -> some View {
content.background(Color.red)
}
}
extension View {
func makeTheBackGroundRed() -> some View {
self.modifier(RedBackGroundModifier())
}
}
struct ContentView: View {
var body: some View {
Text("Hello, World!").makeTheBackGroundRed()
}
}

What is Content in SwiftUI?

In the documentation, I see Content in a different context:
/// A modifier that can be applied to a view or other view modifier,
/// producing a different version of the original value.
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ViewModifier {
/// The content view type passed to `body()`.
typealias Content
}
and here
/// A view that arranges its children in a vertical line.
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct VStack<Content> where Content : View {
I can't find in the documentation the proper explanation of what does Content mean. Is there any predefined content usage in SwiftUI?
It's important to understand that SwiftUI makes heavy use of generic types. Before the release of SwiftUI (and Combine), I had never seen any Swift code that makes such heavy use of generics. Almost all of the View-conforming types (and ViewModifier-conforming types) in SwiftUI are generic types.
ViewModifier
So, first let's talk about ViewModifier. ViewModifier is a protocol. Other types can conform to ViewModifier, but no variable or value can just have the plain type ViewModifier.
To make a type conform to ViewModifier, we define a body method that takes a Content (whatever that is) and returns a Body (whatever that is):
func body(content: Content) -> Body
A ViewModifier is essentially just this one method, that takes a Content as input and returns a Body as output.
What's Body? ViewModifier defines it as an associatedtype with a constraint:
associatedtype Body : View
This means we get to pick the specific type known as Body in our ViewModifier, and we can pick any type for Body as long as it conforms to the View protocol.
And what is Content? The documentation tells you it's a typealias, which means we probably don't get to pick what it is. But the documentation doesn't tell you what Content is an alias of, so we don't know anything about what body can do with the Content it receives!
The reason the documentation doesn't tell you is because Xcode is programmed not to show you a public symbol from the SDK if the symbol begins with an underscore (_). But you can see the true definition of ViewModifier, including the hidden symbols, by looking in the .swiftinterface file for SwiftUI. I explain how to find that file in this answer.
Consulting that file, we find the true definition of ViewModifier:
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public protocol ViewModifier {
static func _makeView(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewInputs, body: #escaping (SwiftUI._Graph, SwiftUI._ViewInputs) -> SwiftUI._ViewOutputs) -> SwiftUI._ViewOutputs
static func _makeViewList(modifier: SwiftUI._GraphValue<Self>, inputs: SwiftUI._ViewListInputs, body: #escaping (SwiftUI._Graph, SwiftUI._ViewListInputs) -> SwiftUI._ViewListOutputs) -> SwiftUI._ViewListOutputs
associatedtype Body : SwiftUI.View
func body(content: Self.Content) -> Self.Body
typealias Content = SwiftUI._ViewModifier_Content<Self>
}
There are also some extensions to ViewModifier that define defaults for body, _makeView, and _makeViewList, but we can ignore those.
So anyway, we can see that Content is an alias for _ViewModifier_Content<Self>, which is a struct that doesn't define any interesting public interface, but does (in an extension) conform to View. So this tells us that, when we write our own ViewModifier, our body method will receive some sort of View (the specific type is defined by the framework and we can just call it Content), and return some sort of View (we get to pick the specific return type).
So here's an example ViewModifier that we can apply to any View. It pads the modified view and gives it a colored background:
struct MyModifier: ViewModifier {
var color: Color
func body(content: Content) -> some View {
return content.padding().background(color)
}
}
Note that we don't have to name the type of View returned by body. We can use some View and let Swift deduce the specific type.
We can use it like this:
Text("Hello").modifier(MyModifier(color: .red))
VStack
Now let's talk about VStack. The VStack type is a struct, not a protocol. It is generic, which means it takes type parameters (just like a function takes function parameters). VStack takes a single type parameter, named Content. This means VStack defines a family of types, one for every type it allows for Content.
Since VStack's Content parameter is constrained to conform to View, this means that for every View-conforming type, there is a corresponding VStack type. For Text (which conforms to View), there is VStack<Text>. For Image, there is VStack<Image>. For Color, there is VStack<Color>.
But we don't normally spell out the full type instance of VStack we're using, and we don't usually have the Content type be a primitive like Text or Image. The whole reason to use a VStack is to arrange multiple views in a column. The use of VStack tells Swift to arrange its subviews vertically, and the VStack's Content type parameter specifies the types of the subviews.
For example, when you write this:
VStack {
Text("Hello")
Button(action: {}) {
Text("Tap Me!")
}
}
you're actually creating an instance of this type:
VStack<TupleView<(Text, Button<Text>)>>
The Content type parameter here is the type TupleView<(Text, Button<Text>)>, which is itself a generic type TupleView with its own type parameter named T, and T here is (Text, Button<Text>) (a 2-tuple, also called a pair). So the VStack part of the type tells SwiftUI to arrange the subviews vertically, and the TupleView<(Text, Button<Text>)> part tells SwiftUI that there are two subviews: a Text and a Button<Text>.
You can see how even this short example generates a type with several levels of nested generic parameters. So we definitely want to let the compiler figure out these types for us. This is why Apple added the some View syntax to Swift—so we can let the compiler figure out the exact type.
This might also be helpful:
private struct FormItem<Content:View>: View {
var label: String
let viewBuilder: () -> Content
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(label).font(.headline)
viewBuilder()
}
}
}
Then use it this way:
FormItem(label: "Your Name") {
TextField("name", text: $bindingToName)
}
Because the viewBinder is the last property of the struct, you can place the contents after the FormItem function call.
I love Rob's answer, which answered my implicit question when I found this SO question, but I thought I could expand on Kontiki's comment, for programmers new to Swift or generics.
This question asks a couple things, specifically:
What is Content in SwiftUI?
Surprisingly, there is no actual Content class or struct or type in SwiftUI (as far as I've seen)! Both examples in the question offer evidence of this.
What do I mean? Content is a generic, kinda like a "variable that holds a type" (though I find that explanation confusing).
Generics are really cool (they're the reason that Swift & XCode autocomplete know that you put, say, strings in an array and not integers), but in this case the generic Content is simply being used to represent an arbitrary type that conforms to the View protocol (as in, a Button and always a Button, not Text). The name Content is completely arbitrary—Apple could equally have called it Foo or MyView, and if you're writing custom types that host their own content, you can choose whatever name you want.
If you've used languages that rely more on classes and subclasses (like Java or C++ or basically every other big, typed language), then it's fair to say, for comparison, that this generic is being used to require all "content" to adhere to the "base class" View (to be clear: View is not a class in SwiftUI; it is a protocol and behaves differently). Except—for a given instance of a control (for example, a specific VStack), Content must always be the same type. Once a Button, always a Button. This is why sometimes you need to use AnyView.
All of this explained practically
How do I know all of this? The second example:
/// A view that arranges its children in a vertical line.
#available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
public struct VStack<Content> where Content : View {
This code declares the struct VStack, which is a generic type because it corresponds to an arbitrary struct/class/type that the type's author chose to call Content. Not any arbitrary type, though, because where Content : View limits callers to using types that implement the View protocol.
Rob's answer explains the other example—that Content for a ViewModifier is just some sort of View as well (by examining other documentation).
Looking at the initializer for VStack, you'll see it takes a function that returns Content:
#inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, #ViewBuilder content: () -> Content)
When you create a VStack (or any other control with content), the content you provide is actually in that function—and from there, Swift can determine the concrete ("real") type of Content when it compiles:
VStack { // Function starts at `{`
Text("test")
Text("test 2")
} // Function ends at `}`
As Rob explains above, the concrete type in this function is actually some sort of TupleView. But because VStack is generic (and also limited to implementors of View), all it knows is that you've provided some specific type that implements View—and whatever type that is, we call it Content.
Aside: some View
This also somewhat explains the usage of of some View in SwiftUI: though you could write out the full TupleView type every time you use it, if you added a Button at the end of your VStack, the function's type would change from TupleView<(Text, Text)> to TupleView<(Text, Text, Button)>, which is tedious to change everywhere it's needed. It's easier to say it's some View (as in, "this is a specific type that implements View and ignore everything else about it"). And using some View is safer than using View:
Returning some View has two important differences compared to just returning View:
We must always return the same type of view.
Even though we don’t know what view type is going back, the compiler does.
Aside: multiple returns in a closure?
If you look at our VStack example again:
VStack { // Function starts at `{`
Text("test")
Text("test 2")
} // Function ends at `}`
You'll noticed that the function we use seems to have 2 return values (two Texts), using the implicit return syntax for Closures. But notably, this implicit return only works for functions with one expression (the feature is called Implicit Returns from Single-Expression Closures!). For example:
func foo(n: Int) -> Int {
n * 4
}
How can we return two things?
How Well Do You Know SwiftUI? describes it:
But inside a Trailing Closure, it still wouldn’t be possible to put views one after another in a declarative way. If you notice in the HStack init method above, the last parameter that is a function of type ( ) -> Content has a #ViewBuilder annotation. This is a new Swift 5.1 feature called Function Builders, which is what makes this syntax possible.
In short, the trailing closure used in VStack has the #ViewBuilder annotation, which is a function builder (Result Builder) that implicitly constructs TupleViews for your UI.

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.