How does Codable fit in with iOS restoration process? - ios

In AppDelegate.swift I have:
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
And iOS will call my encodeRestorableState() & decodeRestorableState() class methods during state restoration.
How does Codable work with respect to state restoration? What does iOS call and how do I tie in my Codable structs and classes?

encodeRestorableState(with:) passes you an instance of NSCoder. Any variables you require to restore your state must be encoded here using encode(_:forKey:) with this coder and must therefore conform to Codable.
decodeRestorableState(with:) passes you this same Coder into the function body. You can access the properties in the decoder with the key you used when they were encoded and then set them to instance variables or otherwise use them to configure your controller.
e.g.
import UIKit
struct RestorationModel: Codable {
static let codingKey = "restorationModel"
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
}
struct CustomThing: Codable {
let someOtherStringINeed = "another string"
}
class ViewController: UIViewController {
var someStringIDoNotNeed: String?
var someStringINeed: String?
var someFlagINeed: Bool?
var someCustomThingINeed: CustomThing?
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
let restorationModel = RestorationModel(someStringINeed: someStringINeed,
someFlagINeed: someFlagINeed,
someCustomThingINeed: someCustomThingINeed)
coder.encode(restorationModel, forKey: RestorationModel.codingKey)
}
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
guard let restorationModel = coder.decodeObject(forKey: RestorationModel.codingKey) as? RestorationModel else {
return
}
someStringINeed = restorationModel.someStringINeed
someFlagINeed = restorationModel.someFlagINeed
someCustomThingINeed = restorationModel.someCustomThingINeed
}
}

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

Saving Information Across ViewControllers

I would like to maintain my var removedIDs = [String]() even when I exit a viewController. I have checked off "Use Storyboard ID" for all Restoration IDs in my StoryBoard. Yet when I navigate away from my viewController, I still lose the contents of removedIDs.
In my AppDelegate, I have written:
func application(_ application: UIApplication, shouldSaveApplicationState coder: NSCoder) -> Bool {
return true
}
func application(_ application: UIApplication, shouldRestoreApplicationState coder: NSCoder) -> Bool {
return true
}
And in my MainTextView, the controller that holds removedIds, I have the extension:
extension MainTextView {
override func encodeRestorableState(with coder: NSCoder) {
super.encodeRestorableState(with: coder)
coder.encode(removedIDs, forKey: "removed")
}
override func decodeRestorableState(with coder: NSCoder) {
func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
removedIDs = coder.decodeObject(forKey: "removed") as? [String] ?? []
}
}
}
I might add that the contents of removedIDs is filled through the following report function:
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let more = UITableViewRowAction(style: .default, title: "Report") { action, index in
self.removedIDs!.append(self.comments[indexPath.row].snap)
What piece of the restoration state process am I missing to allow Xcode to hold my IDs?
What you are trying to do is to save application state, while you really need to save some application data. To do that you can use UserDefaults.
For example something like this:
var removedIds: [String]? {
get { return UserDefaults.standard.value(forKey: "removedIds") as? [String] }
set {
if newValue != nil {
UserDefaults.standard.set(newValue, forKey: "removedIds")
}
else {
UserDefaults.standard.removeObject(forKey: "removedIds")
}
}
}
public func add(removedId: String) {
guard var list = removedIds else { // Nothing saved yet
removedIds = [removedId] // Save array with 1 item
return
}
list.append(removedId) // Add new item
removedIds = list // Save
}
And then you can:
Add an item to the list of stored IDs:
add(removedId: self.posts[indexPath.row].id)
You can also overwrite list:
removedIds = [self.posts[indexPath.row].id]
Get list of previously saved removed ids:
var x = removedIds
Removed all IDs from storage:
removedIds = nil

Calling protocol's mutating function generates "has no member 'functionName'" error

Here's my Protocol:
protocol RequestLocationAuthorizationPresenterProtocol {
mutating func handleLoad(for view: RequestLocationAuthorizationViewProtocol)
func handleGivePermissionAction()
}
Here's my protocol implementation:
struct RequestLocationAuthorizationPresenter: RequestLocationAuthorizationPresenterProtocol {
private let interactor: RequestLocationAuthorizationInteractorProtocol
private let router: RequestLocationAuthorizationRouterProtocol
private weak var view: RequestLocationAuthorizationViewProtocol!
init(with interactor: RequestLocationAuthorizationInteractorProtocol,
router: RequestLocationAuthorizationRouterProtocol) {
self.interactor = interactor
self.router = router
}
mutating func handleLoad(for view: RequestLocationAuthorizationViewProtocol) {
self.view = view
}
func handleGivePermissionAction() {
self.interactor.requestAuthorization { result in
switch result {
case .success:
self.router.goToWeatherView()
case .error:
self.view.presentAlert(with: "Error", message: "The app needs your location in order to work.")
}
}
}
}
When I call the function 'handleLoad' on my View class, it compiles perfectly. But, when I call it from a mock struct it gives me this error: "Value of type 'RequestLocationAuthorizationPresenterProtocol?' has no member 'handleLoad'"
Here's my view class:
class RequestLocationAuthorizationView: UIViewController {
private let presenter: RequestLocationAuthorizationPresenterProtocol
init(with presenter: RequestLocationAuthorizationPresenterProtocol) {
self.presenter = presenter
super.init(nibName: "RequestLocationAuthorizationView", bundle: .main)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func presentAlert(with title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
self.present(alert, animated: true, completion: nil)
}
#IBAction func givePermissionButtonPressed(_ sender: Any) {
self.presenter.handleGivePermissionAction()
}
}
And here's my mock struct:
class RequestLocationAuthorizationViewMock: RequestLocationAuthorizationViewProtocol {
private let expectation: XCTestExpectation!
private let expectedTitle: String!
private let expectedMessage: String!
private let presenter: RequestLocationAuthorizationPresenterProtocol!
init(with expectation: XCTestExpectation? = nil,
expectedTitle: String? = nil,
expectedMessage: String? = nil,
presenter: RequestLocationAuthorizationPresenterProtocol? = nil) {
self.expectation = expectation
self.expectedTitle = expectedTitle
self.expectedMessage = expectedMessage
self.presenter = presenter
}
func callPresenterHandleLoad() {
self.presenter.handleLoad(for: self)
}
func callPresenterHandleGivePermissionAction() {
self.presenter.handleGivePermissionAction()
}
func presentAlert(with title: String, message: String) {
if title == self.expectedTitle && message == self.expectedMessage {
self.expectation.fulfill()
}
}
}
When I change my implementation to be a class instead of a struct and remove the mutating word, it also works perfectly. I've tried to look for similar errors, but I had no luck. I'm on Xcode 10.1 and using Swift Compiler 4.2.
Any thoughts about this issue are welcome.
After looking more closely to the issue, I realize that I'm trying to call a mutating function a constant value (let) and that's why it was not working. The problem here was the compiler giving me a non-sense error. I changed my property from let to var and now it works.
presenter is an optional (even an implicit unwrapped optional is an optional), you have to add a question mark for optional chaining
func callPresenterHandleLoad() {
self.presenter?.handleLoad(for: self)
}
But the error is misleading, now you get the real error
Cannot use mutating member on immutable value: 'presenter' is a 'let' constant
so you have to declare presenter as variable and – highly recommended – as regular optional
private var presenter: RequestLocationAuthorizationPresenterProtocol?

Proper way of setting delegates in MVVM

I would like to know what is a proper way of setting delegates in the ViewModel in MVVM pattern in Swift.
I'm instantiating the ViewController from another class:
let viewModel = DashboardViewModel()
let viewController = DashboardViewController(viewModel: viewModel)
My ViewModel:
protocol DashboardViewModelType {
var items: [Item] { get }
var reloadDelegate: DashboardDataReloadDelegate? { get set }
}
protocol DashboardDataReloadDelegate: class {
func reloadData()
}
class DashboardViewModel: DashboardViewModelType {
var items: [Item] = []
weak var reloadDelegate: DashboardDataReloadDelegate?
init() {
loadItems()
}
func loadItems() {
let databaseFetcher = DatabaseDaysFetcher()
databaseFetcher.getDays(onData: { (items) in
self.items = items
reloadDelegate?.reloadData() //delegate is nil here
}) { (error) in
print(error)
}
}
}
and ViewController:
class DashboardViewController: UIViewController {
var viewModel: DashboardViewModelType?
init(viewModel: DashboardViewModelType) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
self.viewModel!.reloadDelegate = self // it is executed after
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension DashboardViewController: DashboardDataReloadDelegate {
func reloadData() {
print("data reloaded")
}
}
So the main problem is that if I want to inject the viewModel in another class I'm instantiating the viewModel when delegate is not yet set. Would it be better to declare loadItems inside the DashboardViewModelType protocol and then call this function from the init or viewDidLoad inside the ViewController?
Yes, you could inject DatabaseDaysFetcher in the init for the DashboardViewModel and then as you say, move loadItems to the DashboardViewModelType protocol.
Then when you call loadItems, it should callback in to the caller.
Then use [weak self] in the loadItems callback.
This would negate the need for the delegate.
protocol DashboardViewModelType {
init(databaseFetcher: DatabaseDaysFetcher)
func loadItems(completion: ([Item]) -> Void, error: (Error) -> Void)
}
final class DashboardViewModel: DashboardViewModelType {
private var databaseFetcher: DatabaseDaysFetcher
init(databaseFetcher: DatabaseDaysFetcher) {
self.databaseFetcher = databaseFetcher
}
func loadItems(completion: ([Item]) -> Void, onError: (Error) -> Void) {
self.databaseFetcher.getDays(onData: { (items) in
completion(items)
}) { (error) in
onError(error)
}
}
}

Custom class archiving in Swift

I would like to archive and unarchive a custom object in swift, something like this:
class Line : NSObject, NSCoding {
var start : CGPoint
init(start _start: CGPoint) {
start = _start
}
required init(coder aDecoder: NSCoder) {
// 1 - compile time error
self.start = aDecoder.decodeObjectForKey("start") as CGPoint
}
override init() {
}
func encodeWithCoder(aCoder: NSCoder) {
// 2 - compile time error
aCoder.encodeObject(start, forKey: "start")
}
}
The 1. compile time error is:
Type 'CGPoint' does not conform to protocol 'AnyObject'
The 2. compile time error is:
Extra argument 'forKey' in call
How to archive and unarchive a CGPoint, I know CGPoint is a struck and this is the problem, but how to solve it?
thanks in advance.
You can archive/unarchive a CGPoint using :
encodeCGPoint:forKey:
and
decodeCGPointForKey:
More information here : https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSCoder_Class/index.html#//apple_ref/occ/instm/NSCoder/encodeCGPoint:forKey:
May be you can convert CGPoint to NSValue.
func encodeWithCoder(aCoder: NSCoder) {
var cgPointAsObject:NSValue = NSValue(CGPoint: start)
aCoder.encodeObject(cgPointAsObject, forKey: "start")
}
Similarly reverse process convert NSValue to CGpoint while decoding.
In swift3 there is some func changed, to do like this:
class UserModel: NSObject,NSCoding {
var username:String = ""
var password: Int = 0
required init(coder aDecoder: NSCoder) {
super.init()
}
override init() {
}
func encode(with aCoder: NSCoder) {
}
}

Resources