I'm creating a custom generic TableView model to work with MVVM and RxSwift. I'm aware of the RxTableViewSectionedReloadDataSource class but not willing to use it now in my project.
The model is created and working
typealias TableViewModel = TableModel<CellDescriptor>
class TableModel<T> {
var sections = [SectionModel<T>]()
func add(item: SectionModel<T>) {
sections.append(item)
}
// More funcs there...
}
I created an instance inside my ViewModel as such :
var tableViewModel = Variable<TableViewModel>(TableViewModel())
And then listen to event in my viewController
viewModel.tableViewModel.asObservable().subscribe({ [weak self] value in
self?.tableView.reloadData()
}).addDisposableTo(dispose)
Several questions here (I'm fairly new to FRP)
:
How can I "emit" an event to trigger my subscription in the viewController from my custom class?
I know that a solution would be to create my sections array as a RxSwift Variable() and then listen to it directly, but wonder if there's a way to make a class (or struct) itself Observable.
Is there a better approach to this problem ? I went through the RxSwift playground and example project but it sounds like there's a thousand ways to do the same thing. Maybe an Observable is not what i would be looking for. :)
Don't make the view model itself an Observable. It's unnecessary complexity and it's just not intuitive. If you really wanted to, then take a look at how Variable is implemented, which should give you an idea of how to do it.
Instead, just use a subject (such as Variable) to hold onto your data within your view model.
Related
What I'm trying to achieve is to have a mutable Array in viewModel that can also be observed by Controller and reload tableView when its value changes.
I tried to use BehaviorRelay but looks like it's immutable. I can't delete and insert data inside it's value.
The best thing I managed to write is this custom ObservableObject class:
final class CustomObservableObject<T> {
var value: T {
didSet {
listener?(value)
}
}
init(value: T) {
self.value = value
}
var listener: ((T) -> Void)?
func bind(listener: #escaping ((T) -> Void)) {
self.listener = listener
}
}
which works exactly how I want. Is there anything I can use in the Combine or RxSwift framework like this class?
Thanks in advance
A BehaviorRelay is built for the specific purpose of storing state and allowing you to imperatively update it which is what you are asking for.
The BehaviorRelay type is a wrapper around the BehaviorSubject type and provides a different interface, one that does not allow the internal subject to emit an error or completed event. The Element contained in the BehaviorSubject is mutable. However, in order to mutate it you must first extract it into a var with value and then push the updated value back in with accept(_:). This will help ensure that no mutations are missed and allows you to batch up multiple mutations into a single update. These are things missing in the class you wrote.
Please always remember though:
Subjects [and Relays] provide a convenient way to poke around Rx, however they are not recommended for day to day use.
-- Introduction to Rx
As you learn more about, and grow more comfortable with, the reactive paradigm you will find yourself reaching for this particular crutch less often.
I am trying to get my head round how i can achive the following using the MVVM design pattern with SwiftUI.
I want to have only 1 instance of a networking operation queue (using OperationQueue) where any view model that needs to send any networking requests but i have heard that creating a Singleton is not preferred and i should be passing the networking queue object around where it is needed.
So if i create the instance of the network operation queue in the Scene Delegate and pass them into the ContentView initialiser and store it in an object there to then pass into the views then created.
This doesnt seem like good MVVM design practice as from what i understand the View should Own the ViewModel only?
What is the best way of achieving this?
Edit: thought a bit more about this and I can pass it into the view via its constructor and then within the constructor I can create the view model and pass it straight through so the view doesn’t own anything.
But I still will require a singleton so how can I pass the singleton as a dependency injection rather than using it globally?
Thanks
We shouldn’t create singletons for the single reason of being an easy way to get global variables, but it doesn’t mean we should never use them.
In your case, if I understood correctly, you are basically creating a service that can be used by the entire application. You could either A) create a reusable class with the networking functions you need (and instantiate anywhere you need it) or B) create a class with a singleton instance in it, that can be easily accessed anywhere.
A singleton would be a better choice if you need to keep some state common to all callers, or if you need to maintain a waiting queue, for example.
Option A
class NetworkService {
init() {
// init
}
// Your properties and methods
func someFunction() {}
}
Usage in a ViewModel:
let networkService = NetworkService()
networkService.someFunction()
Option B
class NetworkService {
static let shared = NetworkService()
private let queue : Any?
// Your properties and methods
func someFunction() {}
}
Usage:
NetworkService.shared.someFunction()
Either way, this is still MVVM. The data is not related to any specific view, nor to a specific model; it's simply a service you would call in any ViewModel that needs it.
I'm trying to use the MVC in an app loading a url into a webView. I'm struggling with how to/whether to define the back, forward, reload, etc... functions in the model or the viewController. I'm pretty sure they belong in the model, but then how do I call them in the IBAction for the corresponding button?
In trying to call the class function in the IBAction, first I have to create an instance of WebViewLoadRequest in each IBAction which seems extraneous. I can't (and probably shouldn't) create a global instance of WebViewLoadRequest because self isn't available in the property initializer to reference the UIWebView Outlet
class WebViewLoadRequest {
var outlet: UIWebView
var url : String
private var address: URL
init(outlet: UIWebView, url: String) {
self.outlet = outlet
self.url = url
self.address = URL(string: url)!
}
func load() {
self.outlet.loadRequest(URLRequest(url:address))
}
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
func forward() {
if outlet.canGoForward {
outlet.goForward()
}
}
func refresh() {
outlet.reload()
}
}
To simplify:
How can I write the following function in the model layer and implement it in the ViewController?
//outlet refers to a UIWebView
func back() {
if outlet.canGoBack {
outlet.goBack()
}
}
Or should this strictly be a function of the view controller? I realize essentially just the view is being changed, but there's a lot going on behind the scenes in the goBack operation that I'm not sure belongs in the ViewController
You are confusing two things here IMO, there is object oriented programming which you described in your question. There is also another concept which is commonly used and also very common in Swift, this concept is MVC. Model, View, Controller. This is basically a seperation of powers. In practice this comes down to:
Model - custom class with all the logic, in your case the WebViewLoadRequest
View - defined in a 'storyboard'
Controller - Will be a subclass of UIViewController, this subclass also has a reference to your model, all the IBActions and IBOutlets to connect to and update your UI.
You can learn more about MVC here:
https://www.raywenderlich.com/132662/mvc-in-ios-a-modern-approach
Also I would strongly recommend watching the C193p course from Standford by Paul Hegarty, this teaches you all there is to know. For example the 4th week is about MVC
https://itunes.apple.com/us/course/developing-ios-10-apps-with-swift/id1198467120
EDIT:
Basically your construction is a construction of delegation. The normal way to solve this issue is by creating a delegate property on your model-class, the delegate implements a certain protocol with for example a 'canIGoBack'-function. The model can request extra data from the delegate by calling that delegate function. Apple has something about delegation in their swift manual (check the 'Delegation' part:
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html
I believe Paul Hegarty also discussed it.
However, in this case it is also fine to do the canGoBack in the viewcontroller and conditionally call your model. MVC is only a guideline, there are many reasons to sometimes do logic in a viewcontroller, the biggest reason is probably if it makes shit easier. Implementing delegation only for a canGoBack is most of the times just silly.
I'm using RxSwift and I'm trying to extend another library I'm using to make something observable.
The library basically calls a delegate method every time a value changes, and I want to hook into this and whenever it calls the delegate, also add the new value to an observable sequence I want to create in my subclass.
I've seen how you can create observable sequences, but in each example next events are sent to the observer within the block given to the Observable.create method. I have no idea how I can add things to the observable sequence from "outside" this block passed to create.
I just want to create something that I can observe or can drive things with RxSwift, and manually add to the sequence at certain points.
I'd be very grateful if someone could point me in the right direction, as I'm very new to this.
Observable is readonly interface. Sequences created by Observable.create can only produce the value(s) given at construction time and nothing more. You can't "add things" to it, to use your words. Speaking in RxSwift terms, you can't do away with just Observable interface, you need also ObserverType - it must also observe your mutating value. There is more than one way to do it in RxSwift, but i think that you need PublishSubject:
let value = PublishSubject<YourType>()
let disposer = DisposeBag()
init() {
value.subscribe(onNext: { (newValue) in
// use newValue ...
}).addDisposableTo(disposer)
}
func yourDelegateHandler(newValue: YourType)
{
value.onNext(newValue)
}
In an app that I am working for, I need array variable that can be used in all UIViews. Currently when the array is changed in a view it is stored in the database, and when I need the updated version of the array in the previous view, I use the viewWillAppear method and retrieve the updated array from the database. While going to another view by a segue, I use the passing data by prepareForSegue, but if I use the back button, or just change the screens via tab bar, I use the viewWillAppear and a query.
Is there any way that when the array is created in a view, the data in it will be accessible in all views?
As I've stated in my comment, singletons are generally frowned upon for a myriad of reasons. However, there is much debate on this topic:
What is so bad about singletons?
Having said that, the best way I know to make a variable globally available for the session is by creating a singleton.
struct myArray {
static var data: [Int] = []
}
You could set this singleton up to fetch the records using CoreData and store the current working version in a static variable for quick access.
note: I am really very curious to see some other approaches.
Singleton
Singleton is basically a global variable that you can use them in any views, but some developers experience some bugs and difficulties, use it at your own risk, I recommend this method when you're definite that you will use that data a lot (STILL RISKY), but this method is like goddess of data handling :).
Create a NSObject subclass and call it DataManager.swift (I call it data manager cause it handle data.) as following:
import UIKit
class DataManager: NSObject {
//Store Data Globally
static var someData: NSArray! //This Boolean, you can choose whatever you want.
}
the static is what keep your data live.
Now you can store and receive someData from anywhere like you handle any data type like this.
//Store
DataManager.someData = []
//Receive
print(DataManager.someData)