Custom init UIViewController query - ios

I am hoping you can help me understand why the below code segment works and the other does not. I am wanting to create a custom initialiser for my UIViewController which has a custom nib file I have created.
My issue is that I want to understand why in the below code the references to newMember and facebookLogin are retained when I hit the viewDidLoad method but in the other segment of code they are not? Can anyone shed some light as to why this would be the case?
Working Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Broken Code Block
class RegistrationFormViewController: MiOSBaseViewController
{
var newMember:Member!
var facebookLogin: Bool = false
init(member: Member, facebookLogin: Bool = false) {
self.newMember = member
self.facebookLogin = facebookLogin
super.init(nibName: "RegistrationFormViewController", bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let view = self.view as! RegistrationFormView
view.loadViewWith(member: newMember)
view.customNavBarView.backActionBlock = {
self.newMember.deleteEntity(MiOSDataContext.sharedInstance.managedObjectContext)
_ = self.navigationController?.popViewController(animated: true)
return
}
}
}
Thanks,
Michael

Related

Clean Swift dataStore and routing

I am develop am app in Clean architecture, I want pass data to main screen after user logged in, here is my router file:
import UIKit
protocol LoginRoutingLogic: class {
func routeToRegisterController()
func routeToRecoveryPassword()
func routeToMainPage()
}
protocol LoginDataPassing: class {
var dataStore: LoginDataStore? { get }
}
final class LoginRouter: LoginRoutingLogic, LoginDataPassing {
weak var viewController: LoginController?
var dataStore: LoginDataStore?
func routeToRegisterController() {
let storyboard = UIStoryboard(name: Constants.Identifiers.registerControllerIdentifier, bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: Constants.Identifiers.registerControllerIdentifier) as? RegisterController {
viewController?.navigationController?.pushViewController(viewcontroller, animated: true)
}
}
func routeToRecoveryPassword() {
let storyboard = UIStoryboard(name: Constants.Identifiers.forgotPasswordControllerIdentifier, bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: Constants.Identifiers.forgotPasswordControllerIdentifier) as? RecoveryPasswordController {
viewController?.present(viewcontroller, animated: true, completion: nil)
}
}
func routeToMainPage() {
let storyboard = UIStoryboard(name: "MainPageController", bundle: nil)
if let viewcontroller = storyboard.instantiateViewController(withIdentifier: "MainPageController") as? MainPageController {
viewController?.navigationController?.pushViewController(viewcontroller, animated: true)
}
}
}
my user model, that I want to pass the values to the main page, making the call works just fine:
import Foundation
struct User: Codable {
var token: String?
var name: String?
var email: String?
var password: String?
var statusCode: Int?
}
struct LoginError: Codable {
}
extension User {
static func parse(responseData: Data?) -> User? {
var user: User?
guard let data = responseData else {
return user
}
do {
let decoder = JSONDecoder()
user = try decoder.decode(User.self, from: data)
} catch let err {
print("Error: ", err)
}
return user
}
}
and the viewController that I want to pass the username to the username label:
import UIKit
protocol MainPageDisplayLogic: class {
func getData(viewModel: LoginModel.Fetch.ViewModel)
}
final class MainPageController: UIViewController {
var interactor: MainPageBusinessLogic?
var router: (MainPageRoutingLogic & MainPageDataPassing)?
var builder = MainPageBuilder()
// MARK: Object lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setUpView()
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
// MARK: Setup
private func setup() {
let viewController = self
let interactor = MainPageInteractor()
let presenter = MainPagePresenter()
let worker = MainPageWorker()
let router = MainPageRouter()
viewController.interactor = interactor
viewController.router = router
interactor.presenter = presenter
interactor.worker = worker
presenter.viewController = viewController
router.viewController = viewController
router.dataStore = interactor
}
}
extension MainPageController: MainPageDisplayLogic {
func getData(viewModel: LoginModel.Fetch.ViewModel) {
let name = viewModel.name
builder.usernameLabel.text = name
}
}
extension MainPageController: ViewCodeProtocol {
func setUpView() {
viewHierarchy()
makeConstraits()
setupViewNavigationBar()
}
func viewHierarchy() {
view.addSubview(builder.usernameLabel)
}
func makeConstraits() {
builder.usernameLabelConstraits()
}
func setupViewNavigationBar() {
navigationController?.setNavigationBarHidden(false, animated: true)
}
}
And the router from the main page, I could no properly figure that datastore thing yet, how do a pass the data between controllers in clean swift? at least without userdefaults.
import Foundation
protocol MainPageRoutingLogic: class {
}
protocol MainPageDataPassing: class {
var dataStore: MainPageDataStore? { get }
}
final class MainPageRouter: MainPageRoutingLogic, MainPageDataPassing {
weak var viewController: MainPageController?
var dataStore: MainPageDataStore?
}
I have figured that out, I just simply have to use the dataStore references in the destination ViewController and retrieve the data.

How do I initialise view controller with custom data?

I'm using present to open a view controller.
self.storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
self.myView = self.storyboard?.instantiateInitialViewController() as? myViewController
self.present(self.myView!, animated: true) { _ in }
Then in the view controller I'm trying to get data from initialiser
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
override init(dataA: String, dataB: String) {
self.dataA = dataA
self.dataB = dataB
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
I get an error Initializer does not override a designated initializer from its superclass
How should I override init and pass data in this case?
I do not think there is a way, other than not using storyboards. Even Swinject does that in a separate method.
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
fun initialize(dataA: String, dataB: String) {
self.dataA = dataA
self.dataB = dataB
}
}
and in the other class
self.storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
self.myView = self.storyboard?.instantiateInitialViewController() as? myViewController
self.myView.initialize(dataA: dataA, dataB: dataB)
self.present(self.myView!, animated: true) { _ in }
or you can do it in other way and create a static func, that will return an instance. But I prefer the first way.
class myViewController: UIViewController {
var dataA: String?
var dataB: String?
static func instance(dataA: String, dataB: String) -> myViewController {
//You could pass storyboard too
let storyboard = UIStoryboard(name: "myStoryboard", bundle: nil)
let myView = storyboard.instantiateInitialViewController() as? myViewController
self.dataB = dataB
self.dataA = dataA
return myView
}
}

Why passed value is nil in viewcontroller

I know this is simple question but I couldn't understand for hours what's the problem here.
enum TypeOfAlert {
case success, error, warning, confirm
}
class MainAlertView: UIViewController {
var mode: TypeOfAlert!
var transitioner : CAVTransitioner
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.transitioner = CAVTransitioner()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
self.modalPresentationStyle = .custom
self.transitioningDelegate = self.transitioner
setupAlertView()
}
private func setupAlertView() {
print(mode) // result is nil
}
convenience init() {
self.init(nibName:nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
}
I have this code to show alertdialog
I opening MainAlertView by pressing a button
let vc = IFOMainAlertView()
vc.delegate = self
vc.mode = TypeOfAlert.confirm
self.present(vc,animated: true)
but when I am printing or debuging var mode is always nil. Why ?
You have printed var mode before you set it
you are trying to debug at initialisation moment,
when you called
let vc = IFOMainAlertView()
the function init was called, before you called the rest:
vc.delegate = self
vc.mode = TypeOfAlert.confirm
either add a new constructor to your class that takes mode as an argument, or delay debugging the mode attribute to the viewDidLoad method

Sinch video doen't want to work on iOS (Swift)

So basically I want to enable Sinch Video in iOS application.
For testing purposes I've created SinchManaevger which is singleton and I instatiate it in AppDelegate:
class SinchManager: NSObject, SINClientDelegate, SINCallClientDelegate {
static let sharedInstance = SinchManager()
var client: SINClient?
func initSinchClientWithUserId(id: String) {
if client == nil {
if case .Authenticated(let currentUser, _) = SessionManager.sharedInstance.state.value {
self.client = Sinch.clientWithApplicationKey("xyz", applicationSecret: "xyz", environmentHost: "sandbox.sinch.com", userId: currentUser.username)
print("sinchClient")
print(client!)
self.client!.delegate = self
self.client!.setSupportCalling(true)
self.client!.enableManagedPushNotifications()
self.client!.start()
self.client!.startListeningOnActiveConnection()
}
}
}
func clientDidStart(client: SINClient!) {
print("clientDidStart")
self.client!.callClient().delegate = self
}
func clientDidStop(client: SINClient!) {
print("clientDidStop")
}
func clientDidFail(client: SINClient!, error: NSError!) {
print("clientDidFail")
}
func client(client: SINCallClient!, didReceiveIncomingCall call: SINCall!) {
print("didReceiveIncomingCall")
let sinchVC = SinchVC(username: currentUser.username)
let sinchNC = DNMMainNC(rootViewController: sinchVC)
sinchVC.call = call
}
}
And I've created Sinch ViewController which is initialized with username which will be called:
class SinchVC: UIViewController, SINCallDelegate {
private let videoController = SinchManager.sharedInstance.client!.videoController()
private let audioController = SinchManager.sharedInstance.client!.audioController()
private let callClient: SINCallClient
private var call: SINCall!
let username: String
private var mainView: SinchView { return view as! SinchView }
override func loadView() {
view = SinchView()
}
init(username: String) {
self.username = username
self.callClient = SinchManager.sharedInstance.client!.callClient()
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
call.delegate = self
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
if self.call.direction == SINCallDirection.Incoming {
self.audioController.startPlayingSoundFile(self.pathForSound("incoming.wav") as String, loop: true)
}
if self.call.details.videoOffered {
print("video offered")
self.mainView.videoView.addSubview(self.videoController.localView())
self.videoController.localView().contentMode = .ScaleToFill
}
mainView.videoView.addSubview(self.videoController.localView())
mainView.answerButton.addTarget(self, action: #selector(answer), forControlEvents: .TouchUpInside)
mainView.declineButton.addTarget(self, action: #selector(decline), forControlEvents: .TouchUpInside)
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.audioController.enableSpeaker()
}
func pathForSound(string: String) -> NSString {
let nsSt = NSBundle.mainBundle().resourcePath! as NSString
return nsSt.stringByAppendingPathComponent(string)
}
func answer() {
call.answer()
}
func decline() {
call.hangup()
}
func callDidEstablish(call: SINCall!) {
print("callDidEstablish")
}
func callDidEnd(call: SINCall!) {
print("callDidEnd")
}
func callDidProgress(call: SINCall!) {
print("callDidProgress")
self.audioController.startPlayingSoundFile(self.pathForSound("ringback.wav") as String, loop: true)
}
func callDidAddVideoTrack(call: SINCall!) {
print("callDidAddVideoTrack")
mainView.videoView.addSubview(self.videoController.remoteView())
}
}
Problem is when I try to call from my app to other phone with my app nothing happens (didReceiveIncomingCall delegate method doesn't get called at all)
If I try to call from my app to SinchVideo sample app then video call gets initiated normal. But when i call from SinchVideo app to my app nothing happens in my app. So probably i've forgot to add some notification or something to tell my app when the call is incoming. If you could help I would be very grateful. Thanks
EDIT: I managed to make didReceiveIncomingCall work but now call.answer isnt working. (nothing happens when call.answer is called and i see that my phone is ringing)
I am not sure what DNMMainNC does in your did recieve incoming call,
let sinchNC = DNMMainNC(rootViewController: sinchVC) What does DNMMainNC do?
sinchVC.call = call // private var?
But its looks kind of weird to set a private var call from your code, should that not be public or have a constructor like your init but with a call

Passing data between Tab Bar Controllers with NSNotificationCenter in Swift

I'm trying to pass data between tab bar controllers for the firts time, but it doesn't work.
First Controller:
class FirstViewController: UIViewController {
#IBOutlet weak var sentNotificationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateNotificationSentLabel:", name: mySpecialNotificationKey, object: nil)
}
#IBAction func notify() {
NSNotificationCenter.defaultCenter().postNotificationName(mySpecialNotificationKey, object: nil, userInfo:["message":"Something"])
}
func updateNotificationSentLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
sentNotificationLabel.text = messageString
}
}
Here it works ok, the sentNotificationLabel.text is "Something"
Second Controller is similiar, but he's not receiving any notification.
class SecondViewController: UIViewController {
#IBOutlet weak var notificationLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateLabel:", name: mySpecialNotificationKey, object: nil)
}
func updateLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
notificationLabel.text = messageString
}
}
What I am doing wrong? How to change that?
The problem occurs because you are sending the message before the second view controller is registered.
NSNotificationCenter doesn't support sticky notifications - in other words, the message won't be cached. If no-one is listening at the very moment the notification is sent, it's just gone.
The easiest fix is to register your SecondViewController for notification in initialiser. However, than the UILabel is not yet loaded - we are before viewDidLoad callback. The solution is to cache the received message locally and use it to set the label text when it's ready.
class SecondViewController: UIViewController {
#IBOutlet weak var notificationLabel: UILabel!
var cachedMessage : String? = nil
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "updateLabel:", name: mySpecialNotificationKey, object: nil)
}
override func viewDidLoad() {
if let cachedMessage = cachedMessage {
notificationLabel.text = cachedMessage
}
}
func updateLabel(notification:NSNotification) {
let userInfo:Dictionary<String,String!> = notification.userInfo as Dictionary<String,String!>
let messageString = userInfo["message"]
if let notificationLabel = notificationLabel {
notificationLabel.text = messageString
} else {
cachedMessage = messageString
}
}
}
Since controllers are being initialised before they are being displayed, the message should be delivered properly.
Simply add observer in init method
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "addBadge", name: "addBadge", object: nil)
}

Resources