Reload TableViewController from parent - ios

I have a TableViewController, lets call it A, that is in a container view of another view controller, B. I need A to reload it's data when a value changes in B. I also need it to get this changed value from B. Any ideas?

Have you considered using notifications?
So, in B – I would do something like:
// ViewControllerB.swift
import UIKit
static let BChangedNotification = "ViewControllerBChanged"
class ViewControllerB: UIViewController {
//... truncated
func valueChanged(sender: AnyObject) {
let changedValue = ...
NSNotificationCenter.defaultCenter().postNotificationName(
BChangedNotification, object: changedValue)
}
//... truncated
}
Followed up with A looking something like this – where ValueType is simply the type of the value you mentioned:
import UIKit
class ViewControllerA: UITableViewController {
//... truncated
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//...truncated
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "onBChangedNotification:",
name: BChangedNotification,
object: nil)
}
//... truncated
func onBChangedNotification(notification: NSNotification) {
if let newValue = notification.object as? ValueType {
//...truncated (do something with newValue)
self.reloadData()
}
}
}
Lastly – don't forget to remove the observer in A's deinit method:
import UIKit
class ViewControllerA: UITableViewController {
//... truncated
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
//... truncated
}

Related

Delegate always getting nil value in dynamic framework class

I used delegation for passing data to ViewController B to A in dynamic framework . B is my dynamic framework ViewController . A is my app ViewController . I am always set delegate as self in my A class
Without dynamic framework it works perfectly
Class B code : Inside dynamic framework (Using .xib)
import UIKit
public protocol MediaDataDelegate: class{
func mediaDidFinish(controller:
LoginViewController,transactionId:String,returnURL: String)
}
public class LoginViewController: UIViewController {
public var message = ""
public var delegate: MediaDataDelegate?
public init() {
super.init(nibName: "LoginViewController", bundle: Bundle(for: LoginViewController.self))
print("message 1234 :\(message)")
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override open func viewDidLoad() {
super.viewDidLoad()
print("message 1234 :\(message)")
}
public class func logToConsole(_ msg: String) {
print(msg);
}
#IBAction func backToMainBtnTapped(_ sender: UIButton) {
self.delegate?.mediaDidFinish(controller: self, transactionId: "WERTYQWRCT", returnURL: "www.media.com")
}
}
Class A Code:Inside Other App (Using Storyboard)
Click on conduct IPVButton navigate to dynamic framework view controller
I also pass some value to message string but in dynamic framework class getting empty string.
import UIKit
import NBView
class ViewController: UIViewController ,MediaDataDelegate{
var loginVC = LoginViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
LoginViewController.logToConsole("hello media")
}
#IBAction func conductIPVBtnTapped(_ sender: Any) {
loginVC.delegate = self
present(loginVC, animated: true, completion: nil)
}
func mediaDidFinish(controller: LoginViewController, transactionId:
String, returnURL: String) {
print("Trans Id\(transactionId)")
print("return URl \(returnURL)")
}
}
It is because you are instantiating incorrectly the LoginViewController
You need to do it this way, since you wrote that you have it in a .xib file:
let loginVC = LoginViewController(nibName: yourNibName, bundle: nil)
Always have a weak reference to your delegate, otherwise you will have a retain cycle:
weak var delegate: MediaDataDelegate?
Also, you don't need to use public everywhere where you thought it might fit. Use it wisely and when needed. Here you don't need it. Remove it from everywhere in your LoginViewController

How to receive same callback in two ViewControllers when one is opened?

I want to receive the same callback in the ViewController that is opened at in the time that server response in my Swift Application.
I have two ViewControllers. The first ViewController registers a callBack from a class "NetworkService".
The second ViewController is Opened from the first ViewController and the second receives the "NetworkService" from the firstViewController initialized in a variable, and then registers the same callBack.
When I try to receive the callback from the server, if the first ViewController is opened I get the response. If I open the second ViewController and I resend the response I get this correctly in the second ViewController.
BUT if I return to the first ViewController and I get the response, its' only received on the Second ViewController all times.
class NetworkService {
var onFunction: ((_ result: String)->())?
func doCall() {
self.onFunction?("result")
}
}
class MyViewController: UIViewController {
let networkService = NetworkService()
override func viewDidLoad() {
super.viewDidLoad()
networkService.onFunction = { result in
print("I got \(result) from the server!")
}
}
}
I open the secondViewController like:
let vc = self.storyboard!.instantiateViewController(withIdentifier: "second") as! SecondViewController
vc. networkService = networkService
self.navigationController?.pushViewController(vc, animated: true)
And the Second ViewController:
class SecondViewController: UIViewController {
var networkService: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
networkService!.onFunction = { result in
print("I got \(result) from the server!")
}
}
}
How would it be possible to receive the response in the first ViewController again, then return to first ViewController from the second calling the popViewController?
self.navigationController?.popViewController(animated: false)
How about calling the function within viewDidAppear on both ViewControllers so that you get your response every time you switch between the two views? You wouldn't need to pass networkService between the ViewControllers.
override func viewDidAppear(_ animated: Bool) {
networkService!.onFunction = { result in
print("I got \(result) from the server!")
}
}
You can use notification but you will have to register and deregister VC as you switch between views. Other option is to use delegate, you will need to share NetworkService instance. Quick example of how this could work with protocol.
protocol NetworkServiceProtocol {
var service: NetworkService? { get }
func onFunction(_ result: String)
}
class NetworkService {
var delegate: NetworkServiceProtocol?
func doCall() {
self.delegate?.onFunction("results")
}
func update(delegate: NetworkServiceProtocol) {
self.delegate = delegate
}
}
class VC1: UIViewController, NetworkServiceProtocol {
var service: NetworkService?
init(service: NetworkService? = nil) {
self.service = service
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.service?.update(delegate: self)
}
func onFunction(_ result: String) {
print("On Function")
}
}

Parent variable is not updated for the ChildViewController

I have a class which is inherited from BaseViewController.swift. I have defined a flag for core data changes tracking:
var coreDataUpdated: Bool?
I've also added an observer for core data changes in viewWillAppear of BaseViewController.swift
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
Now whenever I notified about changes in core data, I change coreDataUpdated variable to true
#objc func CoreDataUpadated() {
self.coreDataUpdated = true
}
Now in my ChildViewController.swift in viewDidAppear when I check for coreDataUpdated it returns me nil
here is my complete code:
class BaseViewController: UIViewController {
var coreDataUpdated: Bool?
override func viewWillAppear(_ animated: Bool) {
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
}
func CoreDataUpadated() {
self.coreDataUpdated = true
}
}
class ChildViewController: BaseViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
func reloadData() {
if super.coreDataUpdated ?? false { //I always get nil for
super.coreDataUpdated
tableView?.reloadData()
}
}
}
Notice: I'm working on Xcode 9 beta 5 and iOS 11 with swift 4. However this code works fine on the Xcode 8 and iOS 10.3 with swift 3.
Class inheritance does not mean that subclass instances inherit the values assigned to properties in superclass instances.
You have an instance of BaseViewController which has a coreDataUpdated property and instance of ChildViewController, which has a coreDataUpdated property since it inherits from BaseViewController, but it's property value is unrelated to the BaseViewController instance property value; they are different objects.
When you say self.coreDataUpdated = true you are setting the property on the BaseViewController instance.
When you say super.coreDataUpdated you are referring to the coreDataUpdated property in the ChildViewController instance that is defined by it's superclass; since this property has never been assigned a value it is nil.
Since ChildViewController does not override coreDataUpdated super.coreDataUpdated is the same as self.coreDataUpdated in an instance of ChildViewController, so you could re-write that if statement as:
func reloadData() {
if self.coreDataUpdated ?? false {
tableView?.reloadData()
}
}
Hopefully this makes it clearer as to why coreDataUpdated is nil
Rather than using your own Bool, it may be simpler to examine the hasChanges property of your NSManagedObjectContext instance.
Thanks to #Paulw11 comments, I found out with inheritance the value won't be shared. So I use KVC pattern as the solution. this is my code after change
enum coreDataState: String {
case inserted = "inserted"
case deleted = "deleted"
case updated = "updated"
var description: String {
switch self {
case .inserted: return "inserted"
case .deleted: return "deleted"
case .updated: return "updated"
}
}
}
class BaseViewController: UIViewController {
override func viewWillAppear(_ animated: Bool) {
//Core data update
NotificationCenter.default.addObserver(self,
selector: #selector(self.CoreDataUpadated),
name: .NSManagedObjectContextObjectsDidChange,
object: nil)
}
func CoreDataUpdated(_ notification: Notification) {
if let userDictionary = notification.userInfo as? Dictionary<String, Any> {
for KV in userDictionary {
if KV.key != "managedObjectContext" {
NSUserDefaultManager.SaveItem(KV.key, key: coreDataUpdateKey)
}
}
}
}
func CoreDataChanged() -> coreDataState? {
if let currentState = NSUserDefaultManager.LoadItem(coreDataUpdateKey) as? String {
NSUserDefaultManager.RemoveItem(coreDataUpdateKey)
return coreDataState(rawValue: currentState)
}
return nil
}
}
class ChildViewController: BaseViewController {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
reloadData()
}
func reloadData() {
if super.CoreDataChanged() != .deleted {
loadData()
tableView?.reloadData()
}
}
}
Although it is possible to implement it with other patterns such as delegate or override the CoreDataUpdated function in child classes

super init isn't called on all paths before returning from initializer

I made a framework that wraps Alamofire.
In my Framework when testing (in Test target) i have this code that works grate.
import Foundation
#testable import NetworkManager
class MockRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
When i add it as a framework to a different project
import Foundation
import NetworkManager
class JokesRouter: Router {
enum APICalls {
case func1
case func2
}
var calls: APICalls!
init(calls: APICalls) {
self.calls = calls
}
}
I get an error:
super init isn't called on all paths before returning from initializer
So i added Super.init()
init(calls: APICalls) {
super.init()
self.calls = calls
}
And now i get this error:
super.init cannot be called outside of an initializer
Any idea what is the problem?
You'll need to declare an empty public init() {} in Router.
Though it's a cryptic error, the problem is that the subclass cannot call super.init() because that initializer is not accessible outside of the NetworkManager module unless you declare it as public. The automatically-generated init() in Router is not public by default.
It worked in your test class because the #testable import sidestepped the default internal access restriction.
I was trying to create an Inheritance feature of the Swift Programming.
I created the ViewController as super class.
Then I created another class as subclass name as ViewControllerA of super class ViewController
ViewController Class :
import UIKit
class ViewController: UIViewController {
//: public variables
var area: CGFloat?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
public func printArea(){
print("Print area in Super class")
}
public func calculateArea(){
}
}
ViewControllerA as subclass :
import UIKit
class ViewControllerA: ViewController {
var length: CGFloat?
init( len: CGFloat) {
self.length = len
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func printArea() {
print("The are of A \(area!)")
}
override func calculateArea() {
area = length! * length!
}
}
I have got the error.
Must call a designated initializer of the superclass 'ViewController' in subclass init method.
So I declare the empty init method in super class i.e ViewController
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
It worked for me.

How to send a complete closure better

What I want is to send a closures to a UIViewController to tell it what it will do in the end. But when it comes to a package of UIViewControllers it will be a little messy.
class ViewController: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class ViewController2: UIViewController {
private var complete: ()->()
init(complete: ()->()){
self.complete = complete
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here are my UIViewControllers. What want to do is to send a complete closure to UIViewController2, but I have to push UIViewController first. So what I have to do is send the closure to UIViewController, then UIViewController send the closure to UIViewController2. It is not messy when there are only two UIViewControllers. But it will be very messy when there comes out a package of UIViewControllers.
Any better solutions?
You the following code to handle the completion handler
First, create a base class of UIViewController and define the completionHandler
import UIKit
public class MyController: UIViewController {
var completionHandler: ((Bool, String) -> Void)? // Bool and String are the return type, you can choose anything you want to return
public func onComplete(completion : ((Bool, String) -> Void)?)
{
self.completionHandler = completion
}
override public func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override public func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
In ViewController1, you need to call another ViewController like this
class ViewController: MyController {
// Initialization of view objects
override func viewDidLoad()
{
super.viewDidLoad()
//You are loading another ViewController from current ViewController
let controller2 = self.storyboard?.instantiateViewControllerWithIdentifier("controller2") as? MyController
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})
}
//This is your button action or could be any event on which you will fire the completion handler
#IBAction func buttonTapped()
{
self.completionHandler(boolType, controllerName)
}
and where ever, you will create a new ViewController you will need to set its completionHandler by writing the line
controller2?.onComplete({ (finished, event) in
self.completionHandler?(finished, event)
})

Resources