Bind RACComand to UIViewController event - ios

I'm implementing RAC with MVVM pattern in my project and now I came into a doubt.
I have many calls to the server, but all of them are associated to an UIButton and handled in my ViewModel; now I need to make a call to the server when the UIViewController is loaded. Before MVVM I just created a signal in the viewDidLoad method an voilá!, but I'm not sure if is ok to put this in the ViewController.
Now I don't know how to bind a RACSignal to an event in my ViewController, and worst of that, I'm not sure if that is the way following the MVVM pattern.
What I'm doing right now when I make a call to server coming from a user action(from a UIButton) is this:
ViewController*
self.someButton.rac_command = viewModel.executeSomeAction
//On success:
self.viewModel.executeLoginCompleted.skip(1).subscribeNextAs {
(isExecuting: Bool) -> () in
//Do something
}
//On error:
self.viewModel.executeSomeActionError.subscribeNextAs {
(error: NSError) -> () in
//Dd something
}
ViewModel*
var executeSomeAction: RACCommand?
var executeSomeActionError: RACSignal!
var executeLoginCompleted: RACSignal
executeSomeAction = RACCommand(enabled: combineValidationSignals) {
(any:AnyObject!) -> RACSignal in
println("ANY: \(any)")
return self.executeLoginRequest()
}
executeSomeActionError = executeLogin!.errors
executeLoginCompleted = executeLogin!.executing
How should I create a RACSignal or RACCommand when the UIView did load? Of course, following the MVVM pattern.
Thanks

You have two options. One more hacky than the other but easier to implement accross your app.
Option 1:
You can write an extension to UIViewController that uses associatedObjects to add a viewDidLoadCommand: RACCommand property to UIViewController. You then swizzle the viewDidLoad() method of UIViewController so that it executes your command on viewDidLoad():
extension UIViewController {
private struct AssociatedKeys {
static var ViewDidLoadCommand: String = "ViewDidLoadCommand"
}
var viewDidLoadCommand: RACCommand? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.ViewDidLoadCommand) as? RACCommand
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.ViewDidLoadCommand,
newValue as RACCommand?,
UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
)
}
}
}
func swizzled_viewDidLoad() {
self.swizzled_viewDidLoad()
self.viewDidLoadCommand?.execute(nil)
}
}
// In the appDelegate
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
method_exchangeImplementations(
class_getInstanceMethod(UIViewController.self, "viewDidLoad"),
class_getInstanceMethod(UIViewController.self, "swizzled_viewDidLoad"))
return true
}
Option 2
You can just subclass UIViewController and implement the viewDidLoadCommandand call it in viewDidLoad()
class ViewControllerSubClass : UIViewController {
var viewDidLoadCommand: RACCommand?
override func viewDidLoad() {
super.viewDidLoad()
self.viewDidLoadCommand?.execute(nil)
}
}
Whichever method you choose all you have to do then is set the command in the your custom viewControllers init method and and it will get executed on viewDidLoad.

Related

Embed Unity inside iOS in own ViewController

Using Unity 2019.3.0f3 and its Unity as a library feature I'm trying to embed a Unity project inside my iOS application.
Unity officially only supports full screen rendering. Nevertheless I'm looking for a way around that restriction.
In previous versions of Unity i successfully used swift-unity to do the integration. Within this approach it is easy to just get the View where Unity is rendering to (using UnityGetGLView()). I had no problems regarding stability or resources.
Using the new library approach, every time I try to access the UnityView, unity forces it's complete Window as keyWindow.
I tried accessing the UnityView in my own ViewController using
if let unityView = UnityFramework.getInstance()?.appController()?.rootViewController.view {
// insert subview at index 0 ensures unity view is behind current UI view
view?.insertSubview(unityView, at: 0)
}
But that immediately activates the complete unity-window and hides my parenting UITabBarController.
Trying to make the UnityFramework.getInstance()?.appController()?.rootViewController a child of my UITabBarController failed with the same result.
Furthermore it is not possible to add a child ViewController. Only adding subviews seems possible.
Does anybody know where that window-behaviour is located or how i can access the UnityView (or the RootViewController) and use it freely?
I found a solution to the problem based on this approach from the unity forum. Using this approach I'm able to use the UnityViewController as a child in my own TabBarController.
The approach is working for Unity 2019.3.0f3, but I'm not sure if it will work in future versions. It feels like Unity tries to actively prevent such use. Then again I found hints in comments in the library-code that would suggest that a modified ViewController-Hierarchy was at least contemplated e.g. in UnityAppController+ViewHandling.h. But the instructions are unclear and methods with the hinted names don't exist.
Solution
1. Create UnityEmbeddedSwift.swift
The official example App provided by Unity is a real mess. I ended up using the UnityEmbeddedSwift.swift from the linked forum post with additions for pausing. This class encapsulates all Unity-related functionality in one clean class.
//
// UnityEmbeddedSwift.swift
// Native
//
// Created by NSWell on 2019/12/19.
// Copyright © 2019 WEACW. All rights reserved.
//
//
// Created by Simon Tysland on 19/08/2019.
// Copyright © 2019 Simon Tysland. All rights reserved.
//
import Foundation
import UnityFramework
class UnityEmbeddedSwift: UIResponder, UIApplicationDelegate, UnityFrameworkListener {
private struct UnityMessage {
let objectName : String?
let methodName : String?
let messageBody : String?
}
private static var instance : UnityEmbeddedSwift!
private var ufw : UnityFramework!
private static var hostMainWindow : UIWindow! // Window to return to when exiting Unity window
private static var launchOpts : [UIApplication.LaunchOptionsKey: Any]?
private static var cachedMessages = [UnityMessage]()
// MARK: - Static functions (that can be called from other scripts)
static func getUnityRootViewController() -> UIViewController! {
return instance.ufw.appController()?.rootViewController
}
static func getUnityView() -> UIView! {
return instance.ufw.appController()?.rootViewController?.view
}
static func setHostMainWindow(_ hostMainWindow : UIWindow?) {
UnityEmbeddedSwift.hostMainWindow = hostMainWindow
let value = UIInterfaceOrientation.landscapeLeft.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
static func setLaunchinOptions(_ launchingOptions : [UIApplication.LaunchOptionsKey: Any]?) {
UnityEmbeddedSwift.launchOpts = launchingOptions
}
static func showUnity() {
if(UnityEmbeddedSwift.instance == nil || UnityEmbeddedSwift.instance.unityIsInitialized() == false) {
UnityEmbeddedSwift().initUnityWindow()
}
else {
UnityEmbeddedSwift.instance.showUnityWindow()
}
}
static func hideUnity() {
UnityEmbeddedSwift.instance?.hideUnityWindow()
}
static func pauseUnity() {
UnityEmbeddedSwift.instance?.pauseUnityWindow()
}
static func unpauseUnity() {
UnityEmbeddedSwift.instance?.unpauseUnityWindow()
}
static func unloadUnity() {
UnityEmbeddedSwift.instance?.unloadUnityWindow()
}
static func sendUnityMessage(_ objectName : String, methodName : String, message : String) {
let msg : UnityMessage = UnityMessage(objectName: objectName, methodName: methodName, messageBody: message)
// Send the message right away if Unity is initialized, else cache it
if(UnityEmbeddedSwift.instance != nil && UnityEmbeddedSwift.instance.unityIsInitialized()) {
UnityEmbeddedSwift.instance.ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
}
else {
UnityEmbeddedSwift.cachedMessages.append(msg)
}
}
// MARK - Callback from UnityFrameworkListener
func unityDidUnload(_ notification: Notification!) {
ufw.unregisterFrameworkListener(self)
ufw = nil
UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
}
// MARK: - Private functions (called within the class)
private func unityIsInitialized() -> Bool {
return ufw != nil && (ufw.appController() != nil)
}
private func initUnityWindow() {
if unityIsInitialized() {
showUnityWindow()
return
}
ufw = UnityFrameworkLoad()!
ufw.setDataBundleId("com.unity3d.framework")
ufw.register(self)
// NSClassFromString("FrameworkLibAPI")?.registerAPIforNativeCalls(self)
ufw.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: UnityEmbeddedSwift.launchOpts)
sendUnityMessageToGameObject()
UnityEmbeddedSwift.instance = self
}
private func showUnityWindow() {
if unityIsInitialized() {
ufw.showUnityWindow()
sendUnityMessageToGameObject()
}
}
private func hideUnityWindow() {
if(UnityEmbeddedSwift.hostMainWindow == nil) {
print("WARNING: hostMainWindow is nil! Cannot switch from Unity window to previous window")
}
else {
UnityEmbeddedSwift.hostMainWindow?.makeKeyAndVisible()
}
}
private func pauseUnityWindow() {
ufw.pause(true)
}
private func unpauseUnityWindow() {
ufw.pause(false)
}
private func unloadUnityWindow() {
if unityIsInitialized() {
UnityEmbeddedSwift.cachedMessages.removeAll()
ufw.unloadApplication()
}
}
private func sendUnityMessageToGameObject() {
if (UnityEmbeddedSwift.cachedMessages.count >= 0 && unityIsInitialized())
{
for msg in UnityEmbeddedSwift.cachedMessages {
ufw.sendMessageToGO(withName: msg.objectName, functionName: msg.methodName, message: msg.messageBody)
}
UnityEmbeddedSwift.cachedMessages.removeAll()
}
}
private func UnityFrameworkLoad() -> UnityFramework? {
let bundlePath: String = Bundle.main.bundlePath + "/Frameworks/UnityFramework.framework"
let bundle = Bundle(path: bundlePath )
if bundle?.isLoaded == false {
bundle?.load()
}
let ufw = bundle?.principalClass?.getInstance()
if ufw?.appController() == nil {
// unity is not initialized
// ufw?.executeHeader = &mh_execute_header
let machineHeader = UnsafeMutablePointer<MachHeader>.allocate(capacity: 1)
machineHeader.pointee = _mh_execute_header
ufw!.setExecuteHeader(machineHeader)
}
return ufw
}
}
2. Modify AppDelegate.swift
Sets window and launch options needed by UnityEmbeddedSwift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UnityEmbeddedSwift.setHostMainWindow(window)
UnityEmbeddedSwift.setLaunchinOptions(launchOptions)
return true
}
3. Create RootTabBarController.swift
This class sets up the hierarchy.
It is important to use the UnityRootViewController right after calling UnityEmbeddedSwift.showUnity().
The Tab-Switching is not nice, but if it is missing Unity will pause (or freeze?) during loading. The timing seems to depend on the Unity-Projects loading time. It can be faster for small projects and needs more time for larger projects.
import UIKit
class RootTabBarController: UITabBarController, UITabBarControllerDelegate {
var unityNC: UINavigationController?
var nativeNC: UINavigationController?
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
// start unity and immediatly set as rootViewController
// this loophole makes it possible to run unity in the same window
UnityEmbeddedSwift.showUnity()
let unityViewController = UnityEmbeddedSwift.getUnityRootViewController()!
unityViewController.navigationItem.title = "Unity"
unityNC = UINavigationController.init(rootViewController: unityViewController)
unityNC?.tabBarItem.title = "Unity"
let nativeViewController = UIViewController.init()
nativeViewController.view.backgroundColor = UIColor.darkGray
nativeViewController.navigationItem.title = "Native"
nativeNC = UINavigationController.init(rootViewController: nativeViewController)
nativeNC?.tabBarItem.title = "Native"
viewControllers = [unityNC!, nativeNC!]
// select other tab and reselect first tab to unfreeze unity-loading
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2, execute: {
self.selectedIndex = 1
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01, execute: {
self.selectedIndex = 0
})
})
}
// MARK: - UITabBarControllerDelegate
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
// pause unity if unity-tab is not selected
if viewController != unityNC {
UnityEmbeddedSwift.pauseUnity()
} else {
UnityEmbeddedSwift.unpauseUnity()
}
}
}
4. Modify Main.storyboard
Modify the storyboard to start with the RootTabBarController.
For anyone who is still interested in preventing the freezing, I am building on top of aalmigthy's answer:
You do not need to add a TabBar controller and switch between the tabs. All you need to do is:
Add the Unity view as a subview
Send the subview to back
Here's the modified ViewController class (no need for a tab bar):
import UIKit
class HybridViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
UnityEmbeddedSwift.showUnity()
let uView = UnityEmbeddedSwift.getUnityView()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.view.addSubview(uView!)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
self.view.sendSubviewToBack(uView!)
})
})
}
}

How to show a view through protocol?

I want to detective networking state, when networking state changed, show a error view in current controller. But there is a problem by using protocol.
Here is the codes:
private func networkingDetection() {
//This is the detective method in appdelegate
try! reachability.startNotifier()
reachability.whenReachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().hideNetworkingErrorView()
}
}
reachability.whenUnreachable = { [weak self] _ in
DispatchQueue.main.async {
self?.currentViewController().showNetworkingErrorView()
}
}
}
And here is the protocol
protocol NetworkingErrorProtocol {
// I want to show the default view if there is no networkingErrorView, and
when declare a custom view in controller, show the custom view.
//var networkingErrorView: UIView? { get }
func showNetworkingErrorView()
func hideNetworkingErrorView()
}
extension UIViewController: NetworkingErrorProtocol {
func showNetworkingErrorView() {
}
func hideNetworkingErrorView() {
}
}
Anyone can tell me how to figure it out? It's really makes me crazy. Thanks a lot.
The issue with your setup is that conforming UIViewController to your protocol does not allow you to receive that call in your subclass. If you try to override the protocol function in your subclass you will get a compiler error: Declarations from extensions cannot be overridden yet
First off, a note about NotificationCenter. If you need multiple parts of your app to be notified of the change that would be a good way to go. If you only need to tell one controller, this is a classic usage for a delegate.
Here are two ways to get the desired functionality: using the delegate pattern and without.
Let's say Manager is the class where the monitoring is happening:
Using a delegate pattern
class Manager {
weak var networkDelegate : NetworkStatusListener?
func monitorNetworkStatus() {
var reachable = true;
if reachable {
// We can call the delegate directly
networkDelegate?.networkStatusChanged(.connected)
}
else {
networkDelegate?.networkStatusChanged(.disconnected)
}
}
}
And the same Manager without a delegate pattern. This would be the simplest fix for your current implementation issue.
class Manager {
func currentViewController() -> UIViewController { return vc }
func monitorNetworkStatus() {
var maybeAtListener = currentViewController()
// DON't SHIP THIS, but it can be helpful during development to make sure you didn't forget to conform one of your classes
assert(maybeAtListener is NetworkStatusListener, "Oops, did you mean to conform \(currentVC) to NetworkStatusListener")
var reachable = true;
if reachable {
// We can't be sure the controller conforms to the protocol but we can try
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
else {
(maybeAtListener as? NetworkStatusListener)?.networkStatusChanged(.connected)
}
}
}
Then for your view controller
class MyController : UIViewController, NetworkStatusDelegate {
func networkStatusChanged(_ status: NetworkStatus) {
switch status {
case .connected:
// Normal UI
break
case .disconnected:
// No network connect
break;
}
}
}
Also, not directly related to your question but for this example I used a slightly different approach to the protocol design that can be helpful for "status" oriented protocols. Having multiple functions can often become a little more tedious to conform to as protocols get larger.
enum NetworkStatus {
case connected
case disconnected
}
protocol NetworkStatusListener : class {
func networkStatusChanged(_ status: NetworkStatus)
}
Try using reachability class's NSNotificationCenter
add this in appdelegate's didFinishLaunchingWithOptions if you want for whole app
OR add in your specific viewcontroller if you want this in specific Viewcontroller
NotificationCenter.default.addObserver(self, selector:Selector(("checkForReachability:")), name: NSNotification.Name.reachabilityChanged, object: nil);
let reachability: Reachability = Reachability.forInternetConnection();
reachability.startNotifier();
This method called while network state changed .
func checkForReachability(notification:NSNotification)
{
let networkReachability = notification.object as! Reachability;
_ = networkReachability.currentReachabilityStatus()
// do yor additional work here
}

Function Overriding in iOS

I have created BaseClassviewController and all my controllers are derived from this controller. I am doing the following steps:
Set custom delegate in BaseClassViewController.
Implement all function of protocol in BaseClassViewController.
Then I am pushing HomeController derived from BaseClassViewController.
Again I am pushing DetailController also derived from BaseClassViewController.
Now when delegate function is called I should get control in DetailController but I am getting control in HomeController.
So my question is why its not calling top controller at navigation i.e DetailController and is it possible to call delegate functions in both controllers?
P.S I am overriding delegate functions in all child controllers.
EDIT: After reading answers and comments I think I have not been clear that much so adding following code snippet.
In Helper Class:
#objc protocol SampleDelegate: class
{
#objc optional func shouldCallDelegateMethod()
}
class SampleHelper: NSObject
{
var sampleDelegate:SampleDelegate!
static var sharedInstance = SampleHelper()
//It is triggered
func triggerDelegateMethod()
{
sampleDelegate!.shouldCallDelegateMethod()
}
func apiCall()
{
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let usableData = data {
self. triggerDelegateMethod()
}
}
}
task.resume()
}
}
}
In BaseClass
class BaseClassViewController: UIViewController,SampleDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool)
{
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod()
{
//Override
}
}
In HomeController i.e 1st controller to be pushed
class HomeViewController: BaseClassViewController{
override func shouldCallDelegateMethod()
{
//
}
}
In DetailController i.e 2nd controller is pushed after HomeController from HomeController.
class DetailViewController: BaseClassViewController{
override func viewDidLoad()
{
super.viewDidLoad()
SampleHelper.sharedInstance.apiCall()
}
override func shouldCallDelegateMethod()
{
//
}
}
Now my question is when delegate is triggered from helper class it calls shouldCallDelegateMethod in HomeViewController but not in DetailViewController. But DetailViewController is at top of navigation array.
Also is there any possibility I can trigger same function in both controller at a time with delegate only?
In BaseClassviewController you should have a delegate variable/property.
In HomeController and DetailController you need to set that delegate variable/property to self if you want that class to be listening to the delegate callbacks.
The basic problem is that you are using delegate with a singleton.
Setting the delegate in viewWillAppear is not a good solution either. In short, when view controllers are being shown and hidden, the delegate on your singleton will changed all the time.
Don't use delegates with singletons. Use a completion callback. Otherwise you will keep running into problems.
func apiCall(onCompletion: (() -> Void)?) {
let urlString = URL(string: "https://google.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else if let usableData = data {
onCompletion?()
}
}
task.resume()
}
}
called as
SampleHelper.apiCall {
// do something
}
Edit 2
After you posted your code, i realize that you have used the singleton class for delegation.
Delegates allows an object to send a message to another object.
Answer for your query is "No". You can not trigger same function in both controller at a time with delegate.
If you really want to listen an event in both class at a time, i would suggest you to use NSNotificationCenter instead of delegate.
Thats not the correct way to achieve this. I think proper way to set delegate only in respective UIViewController rather than implementing that protocol on BaseViewController and then overriding in child classes. So your implementation should be like.
In HomeViewController
class HomeViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
In DetailViewController
class DetailViewController: BaseClassViewController {
override func viewWillAppear(_ animated: Bool) {
super.viewDidLoad()
SampleHelper.sharedInstance.delegate = self;
}
func shouldCallDelegateMethod() {
// Provide implementation
}
}
Using this imeplementation you will be having only one-to-one communication design pattern, ensuring right UIViewController to be called.

Transform UIApplicationDelegate methods into RxSwift Observables

In RxSwift / RxCocoa you can create a reactive wrapper for a delegate (e.g. UIScrollViewDelegate or CLLocationManagerDelegate) to enable Rx observable sequences for certain delegate methods.
I am trying to implement this for the UIApplicationDelegate method applicationDidBecomeActive:
What I tried so far is pretty straightforward and similar to the DelegateProxy subclasses that are included in RxCocoa.
I created my DelegateProxy subclass:
class RxUIApplicationDelegateProxy: DelegateProxy, UIApplicationDelegate, DelegateProxyType {
static func currentDelegateFor(object: AnyObject) -> AnyObject? {
let application: UIApplication = object as! UIApplication
return application.delegate
}
static func setCurrentDelegate(delegate: AnyObject?, toObject object: AnyObject) {
let application: UIApplication = object as! UIApplication
application.delegate = delegate as? UIApplicationDelegate
}
}
And an Rx extension for UIApplication:
extension UIApplication {
public var rx_delegate: DelegateProxy {
return proxyForObject(RxUIApplicationDelegateProxy.self, self)
}
public var rx_applicationDidBecomeActive: Observable<Void> {
return rx_delegate.observe("applicationDidBecomeActive:")
.map { _ in
return
}
}
}
In my AppDelegate I subscribe to the observable:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// the usual setup
// and then:
application.rx_applicationDidBecomeActive
.subscribeNext { _ in
print("Active!")
}
.addDisposableTo(disposeBag)
return true
}
When I start my app "Active!" gets printed and then I get the following crash in RxCocoa's _RXDelegateProxy_ class:
Does anybody have an idea what the problem might be? Or has anybody successfully implemented something like rx_applicationDidBecomeActive?
It looks like a really tricky issue with RxSwift and memory management.
The default implementation of DelegateProxyType sets an instance of a delegate proxy (in this case, RxUIApplicationDelegateProxy) to the delegate of UIApplication.
It also stores the original AppDelegate as a property called forwardToDelegate so all the delegate methods can still be passed to it.
The problem is that, when the new app delegate is set:
application.delegate = delegate as? UIApplicationDelegate
the original one is deallocated! You can check it by overriding deinit in AppDelegate. The reasons are explained in this answer. And because the property forwardToDelegate is of type assign, your app crashes as the property points to a deallocated object.
I have found a workaround for that. I'm not really sure if it is a recommended way, so be warned. You can override a method from DelegateProxyType in RxUIApplicationDelegateProxy:
override func setForwardToDelegate(delegate: AnyObject?, retainDelegate: Bool) {
super.setForwardToDelegate(delegate, retainDelegate: true)
}
In normal circumstances, you don't want to retain the delegate as it leads to a retain cycle. But in this special case, this is not a problem: your UIApplication object will exist the entire time while your application is alive anyway.

RxSwift subscribe block not called

I'm playing around with RxSwift and I'm stuck with a simple toy programm. My program essentially contains a model class and a viewcontroller. The model contains an observable that gets updated on the main queue after an asynchronous network call, the viewcontroller subscribes in viewDidLoad(). The AppDelegate initializes the model and passes it to ViewController and triggers the network request.
class GalleryModel {
var galleryCount: BehaviorSubject<Int>
init() {
galleryCount = BehaviorSubject.init(value:0)
}
func refresh() {
doAsyncRequestToAmazonWithCompletion { (response) -> AnyObject! in
var counter = 0
//process response
counter = 12
dispatch_async(dispatch_get_main_queue()) {
self.galleryCount.on(.Next(counter))
}
return nil
}
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var galleryModel: GalleryModel?
override func viewDidLoad() {
super.viewDidLoad()
galleryModel?.galleryCount.subscribe { e in
if let gc = e.element {
self.label.text = String(gc)
}
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var galleryModel: GalleryModel?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//do amazon setup
galleryModel = GalleryModel()
if let viewController = window?.rootViewController as? ViewController {
viewController.galleryModel = GalleryModel()
}
return true
}
func applicationDidBecomeActive(application: UIApplication) {
galleryModel?.refresh()
}
The label gets updated only one, it shows "0". I expected the label to get updated twice, showing "0" after the first update and showing "12" after the second update after the processing of the network request. A breakpoint in the dispatch_async block gets hit, but it seems that galleryCount lost its observer. Anybody any idea what's happening or how to debug this?
Best
In case reads this anyone is interested. It was an refactoring error, after renaming variables I stopped passing the observable to the ViewController. Instead I created a new one... facepalm
Here are some useful snippets for subscribe in RxSwift (in Japanese)
For example to subscribe to different events:
let source: Observable<Int> = create { (observer: ObserverOf<Int>) in
sendNext(observer, 42)
sendCompleted(observer)
return AnonymousDisposable {
print("disposed")
}
}
let subscription = source.subscribe { (event: Event<Int>) -> Void in
switch event {
case .Next(let element):
print("Next: \(element)")
case .Completed:
print("Completed")
case .Error(let error):
print("Error: \(error)")
}
}
Clean and Build solved the problems for me

Resources