Passing array of UIViews or structs to UIViewController - ios

In order to fulfill the promise of MVC I have a question regarding creating structs or not.
Right now, an array of customs UIViews are passed to a ViewController which arranges them.
The custom UIView looks for example like this
class TopView: UIView {
private var text: String
private var question: String
private var explanation: String
required init(text: string,...) {
self.text = text
...
}
....
}
Then those UIViews are passed as an array to a View Controller and a function inside will arranges them. Therefore, lots of arrays of UIViews are stored like this
let derivative: [UIView] = [TopView(text: "", ....),
MainBodyView(text:"",...),
BottomView(text:"",...)
]
So, to have a clear and good code structure I want to know if it better to create structs containing all these information and a function inside the ViewController which extracts the information and put them into views. Or are there better ways to do it ?

The arrays of UIViews will contain always a TopView, MainBodyView and a BottomView. The only difference is in the number of MainBodyView. There can be for example 10 oder 5 MainBodyViews.
Passing views around is very messy and completely violates MVC. It sounds like what you really want to pass here is an instance of a custom configuration struct. For example (this is minimal, just expand it as needed):
struct Config {
struct VConfig {
let text: String
}
let topViewConfig: VConfig
let bodyViewConfigs: [VConfig]
let bottomViewConfig: VConfig
}
Now pass the data and let the target view controller worry about what that means for purposes of views.
This makes sense because you, meaning the first view controller who is assembling this data and passing it to the target view controller, are not in charge of the target view controller's views — you have no business knowing anything about them or touching them in any way. Plus, this way, if it happens that the target view controller uses a storyboard to instantiate the actual views, no problem, as you are agnostic about that (and rightly, as it is none of your business).

Related

iOS MVVM with RxSwft: what is the drawback with viewmodel everywhere?

RxSwft is very suitable for iOS MVVM.
Putting viewmodel everywhere, disobeys Law of Demeter ( The Least Knowledge Principle ).
What is the other drawbacks?
Will it leads to Memory Leakage?
Here is an example:
ViewController has a viewModel
ViewModel has some event signals, like the following back event
class ViewModel{
let backSubject = PublishSubject<String>()
}
ViewController has contentView and viewModel, and contentView init with viewModel
lazy var contentView: ContentView = {
let view = ContentView(viewModel)
view.backgroundColor = .clear
return view
}()
and ViewModel's various subject are subscribed in viewController to handle other part view
viewController is a Dispatch center.
ViewModel is Event Transfer station. ViewModel is in everywhere, in Controller, in View, to collect different event triggers.
the code is quite spaghetti
in ContentView, user tap rx event , binds to the viewModel in viewController
tapAction.bind(to: viewModel.backSubject).disposed(by: rx.disposeBag)
user events wires up easily.
But there is memory leakage actually.
So what's the other disadvantages?
ViewModel doesn't break the Law of Demeter but it does break the Single Responsibility Principle. The way you solve that is to use multiple view models, one for each feature, instead of a single view model for the entire screen. This will make view models more reusable and composable.
If you setup your view model as a single function that takes a number of Observables as input and returns a single Observable, you will also remove any possibility of a memory leak.
For example:
func textFieldsFilled(fields: [Observable<String?>]) -> Observable<Bool> {
Observable.combineLatest(fields)
.map { $0.allSatisfy { !($0 ?? "").isEmpty } }
}
You can attach the above to any scene where you want to enable a button based on whether all the text fields have been filled out.
You satisfy the SRP and since object allocation is handled automatically, there's no concern that the above will leak memory.
You are right, there are some drawbacks, if you want just a data-binding, I would suggest to use Combine instead, since no 3rd party libraries need, you have it already. RxSwift is a very powerful tool when you use it as a part of language, not just for data binding.
Some of suggestions from my experience working with RxSwift:
Try to make VMs as a structs, not classes.
Avoid having DisposeBag in your VM, rather make VC subscribe to everything(much better for avoiding memory leaks).
Make its own VMs for subview, cells, child VC, and not shared ones.
Since your VC is a dispatch centre, I would make a separate VM for your content view and make a communication between ContentView VM and ViewController VM through your controller.

How to get a view to dynamically correspond with an object?

I'm writing an app that holds workouts. A Workout object has an array of Exercise objects. Each exercise has a name and a few other fields. I have a parent view with a list, the list holds multiple NewExercise() views which each have 2 TextFields and 2 Pickers. I am trying to find someway that each instance of the NewExercise() is connected to an Exercise object. As you adjust the state properties of a certain NewExercise() view, it simultaneously adjusts of a corresponding Exercise. For example, changing the name in NewExercise()[0] changes the name of Exercise[0]. NewExercise()[1] changes Exercise[1] and so on. Then, when the "Create" button is pressed on the parent view, all the Exercise objects are put into an array.
So my main question is:
How do I get each of these views to correspond with an Exercise? Something like NewExercise()[1] is linked to Exercise[1]. So adjusting the field in the view changes the corresponding attribute of the Exercise.
My code is a bit long to include here but I have a diagram of how it looks and how I would like it to function:
Diagram
Nonetheless, Let me know If it would be useful to provide any code. Or if I'm going in the wrong direction. I'm rather new to swift so anything helps.
You can use #State & #Binding property wrappers to achieve this. Your NewExercise View should have a #State var exercise: Exercise where exercise would look like:
struct Exercise {
var exerciseName: String
// ... other properties here
}
And in your NewExercise View you should have something like this:
struct NewExercise: View {
#State var exercise: Exercise <-- State is owned by the view
var body: some View {
TextField("Placeholder", text: $exercise.exerciseName) <-- This is a binding
}
}
Further Explanation: https://developer.apple.com/videos/play/wwdc2019/226/

iOS / swift - constants in UIViewController in swift

Suppose I have a view model that my controller uses for asking some data or functionality, that view model will Never changes when I instantiated once, right? but in view controllers I navigate with perform segue and so on, I have no access to initializer then I can't use some thing like this:
let myViewModel: MyViewModel
then I have to use this instead:
var myViewModel: MyViewModel!
and I have no good feeling about this, can anyone suggest a good solution? tnx mates :)
That's totally fine, same for your IBOutlets.
From the moment you instantiate the ViewController until you set up the model, it's value is nil. that means it's not a constant even though you don't change it through the lifecycle of the ViewController.
When view controllers are loaded from storyboard, you have no control over initialization (since the controller is not initialized by you), therefore there is nothing else you can do. The other options are similar and there is no real advantage to either of them. It's a subjective decision:
You could declare the variable as a normal optional
var myViewModel: MyViewModel?
but if the controller really cannot work without data being set, I prefer to use ! myself because not setting the data model should be a fatal error.
In some cases you can also go with a default value, e.g.:
var myViewModel = MyViewModel()
My data setters usually look like this:
var model: Model! {
didSet {
loadViewIfNeeded() // to force view be loaded and viewDidLoad called
updateUI() // set UI values from the data model
}
}

Creating Controllers for the UIViews found in a UIViewController

In the iOS world, we're all used to the following pattern:
class UIViewController {
open var view: UIView!
}
A ViewController is obviously a controller controlling the view, which contains a lot of subviews.
Now, I have a lot of subviews that I want to reuse, and I want to enrich them with more functionalities. Think of a UISlider, a UILabel, or a UITableView that react to some events or some changes in the model. These subviews also need to be #IBDesignable with IBInspectable properties for customisation purposes. I also want to share those components through a library as well.
So in a way, I want small controllers controlling those subviews that will end up in the view of the ViewController.
I am thinking of doing this for the UIKit classes:
#IBDesignable
public class CustomSlider: UISlider {
}
That is a nice way to be able to provide the component with customisation options. The downside is that we're using inheritance here (would rather use composition), and I'm not sure if CustomSlider is really considered here a controller or not.
Can anyone tell me what are good practices around creating controllers for subviews that are customisable? Thanks in advance!
EDIT: Specific case for Views that have delegates and datasource:
#objc public class CustomTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
#IBInspectable public var someCustomField: UInt = 0
public override init(frame: CGRect, style: UITableViewStyle) {
super.init(frame: frame, style: style)
dataSource = self
delegate = self
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
dataSource = self
delegate = self
}
// Implement UITableViewDataSource and UITableViewDelegate
}
Is this bad pattern to have datasource = self and delegate = self, or is it ok?
It's a judgement call. I'd say if all you are doing is adding features to a single view class, it is probably better to just use a custom subclass like your CustomSlider example.
If, on the other hand, you're using small suites of objects, like a slider, a text field, a segmented control and a couple of labels, you might want to think about using container views, embed segues, and custom view controllers. Think of that as setting up tiles that manage sets of UI elements. You can put such a view controller anywhere you want, and size it as needed.
You could also create a custom controller object that manages one or more custom views, but there isn't really system "plumbing" for that, so the burden is on you to build mechanisms to support that approach. You have to teach your view controllers to talk to a controller object that isn't a view, but that HAS views inside it.
Apple does what you're talking about in a couple of instances: UITableViewController and UICollectionViewController, and I think they didn't do it right. A UITableViewController or UICollectionViewController can manage only a single view. You can't put a button at the bottom, or a label somewhere, or a segmented control. The content view of one of those specialized view controllers must be the corresponding view object, which limits their usefulness. You can, of course, get around the problem by using container views and embed segues, and that leads me to my suggestion.
EDIT:
As far as making a view object it's own data source, I would call that "a violation of the separation of powers". A table view is a view object, and the data source is a model object. By combining them, you're collapsing the view and the model into one. That's going to create a larger, very specialized object that is less likely to be reusable.
The same goes for making an object it's own delegate. The idea of the delegate pattern is to be able to leave certain decisions about an object's behavior up to somebody else, so that its more flexible. By making it its own delegate you are limiting the table view's range of behavior and making it less reusable.
Neither thing is going to "warp your mind, curve your spine, and make the enemy win the war" but they seem ill-advised.

show a view on 2 viewcontrollers

I'm looking for a way to show a UIView "InventoryView" in 2 view controllers.
I'm working on an inventory system for my game that I trying to make but I need to be able to access it from my main view, where it will go to a InventoryViewController (in this ViewController is my InventoryView) but I also need to be able to access the InventoryView from my BattleViewController where it does not go to my InventoryViewController but where it print the InventoryView on my BattleViewController so I can access everything durning the battle.
Example:
(evrything is dragand drop, the UIView and the UIButtons)
InventoryViewController
class InventoryViewController: UIViewController {
class InventoryView: UIView {
//here are some UIButtons and labels
}
}
BattleViewController
class BattleViewController: UIViewController {
class InventoryView: UIView {
//it should print the Inventory Screen on my BattleViewController
//here are the same properties as it shows in the InventoryViewController
}
}
This is a great example to look at the way OOP programming works best.
Ask yourself the following questions:
What is the purpose of the view?
Are the interactions on the view homogenous across all the instances? (touch events, specific behavior, etc...)
What is the minimum amount of information you need to make the view look the way you want?
Once you have those answers, you can approach the concept of reusability of views safely.
The way to go about it is to subclass UIView, create the necessary elements of your view, setup your constraints (still in the view, either in a nib or programmatically), and implement any behavior that will be consistent across views (For example if the view is a segmented control, every time you click a segment all the others go grey and the one you clicked go blue. Since that's the primary purpose of the segmented control, the code for it should belong to the segmented control).
Chances are you will find the docs very useful: https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIView_Class/
Lastly write a setup method that takes all the information you need and sets up all your graphical elements accordingly. Remember, views should never own their data (they should be templates, the controller will provide the data).
I have no idea what you view looks like but I assume the inventory is represented as an object. Then something like could be a good start:
class InventoryView: UIView {
var inventory: Inventory? {
didSet {
if let newInventory = inventory { //in case we set it to nil
setup(withInventory: newInventory)
}
}
}
private func setup(withInventory inventory: Inventory) {
//do your setup here
}
}
Then in your controller you can call:
let inventoryView = InventoryView()
inventoryView.inventory = myPlayer.inventory
You cannot use a view in two places, at least not within the UI. Every view can be added to only one super view at a time.
If you need the same contents to be displayed twice, create a UIViewController class which's view contains the common UI, create two of those and add them to your UI.

Resources