I am trying to provide in my app the logout functionality, I would like to know if this way is a good approach to continue.Classes involved are described below:
the first one is the AuthViewCoordinator, which class redirects to the user to auth screens
protocol AuthViewCoordinatorDelegate: class {
func authCompleted(coordinator: AuthViewCoordinator)
}
class AuthViewCoordinator: Coordinator {
weak var fromViewController: UIViewController?
weak var navigationController: UINavigationController?
weak var delegate: AuthViewCoordinatorDelegate?
init(fromViewController: UIViewController, delegate: AuthViewCoordinatorDelegate) {
self.fromViewController = fromViewController
self.delegate = delegate
}
func start() {
let authViewController = UIStoryboard.main.instantiateViewController(withIdentifier: "AuthViewController") as! AuthViewController
authViewController.coordinator = self
let navigationController = NavigationController(rootViewController: authViewController)
navigationController.navigationBar.isHidden = true
fromViewController?.present(navigationController, animated: true, completion: nil)
self.navigationController = navigationController
}
func userDidSelectLogin() {
let loginViewController = UIStoryboard.main.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController
loginViewController.viewModel.coordinator = self
self.navigationController?.pushViewController(loginViewController, animated: true)
}
func userDidSelectSignUp() {
let signupViewController = UIStoryboard.main.instantiateViewController(withIdentifier: "SignUpViewController") as! SignUpViewController
signupViewController.viewModel.coordinator = self
self.navigationController?.pushViewController(signupViewController, animated: true)
}
func userDidLogin() {
navigationController?.dismiss(animated: true, completion: nil)
self.delegate?.authCompleted(coordinator: self)
}
func userDidSignUp() {
navigationController?.dismiss(animated: true, completion: nil)
self.delegate?.authCompleted(coordinator: self)
}
}
And the 2nd one is an external class called SessionController. In this class I'm trying to call AuthViewCoordinator().start() inside the function logout immediately after the tokens have been removed to show again the auth screen to the user, but the output is
Use of unresolved identifier 'AuthViewCoordinator'
public class SessionController{
public enum SessionState {
case anonymous
case authenticated
case notAuthenticated
}
let service: Service
let sessionProvider: SessionProvider
convenience public init() {
self.init(service: Service.instance, sessionProvider: SessionProvider.instance)
}
init(service: Service, sessionProvider: SessionProvider) {
self.service = service
self.sessionProvider = sessionProvider
}
public func getMe(completion: #escaping (Error?) -> ()){
service.execute(resource: Login.getMe()) { (result) in
if let error = result.error {
completion(error)
} else if let session = result.value {
print("\n session \(session)\n")
completion(nil)
}
}
}
public func logout() {
self.sessionProvider.removeUserToken()
self.sessionProvider.removeInstanceToken()
self.sessionProvider.removeAnonymousToken()
AuthViewController().start()
}
public func state() -> SessionState {
if let _ = sessionProvider.getUserToken() {
print("###### authenticated #########")
return .authenticated
} else if let _ = sessionProvider.getAnonymousToken() {
print("###### anonymous #########")
return .anonymous
} else {
print("###### notAuthenticated #########")
return .notAuthenticated
}
}
}
Related
I want to trigger an action on button tap with my callback. Also I have presenter and coordinator. But nothing happenes. My code is not working in this closure:
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
In my ViewController I have enum:
enum StartViewControllerButton {
case registrationButtonTapped
case loginButtonTapped
}
callback:
var output: ((StartViewControllerButton) -> Void)?
and selectors:
#objc func registrationButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.registrationButtonTapped)
}
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
My Presenter
class StartModulPresenter: StartModulPresenterProtocol {
var navigationController: UINavigationController
var coordinator: CoordinatorProtocol?
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
coordinator = AuthorizationCoordinator(navigationController: navigationController)
}
//Functions
func openNextScreen() {
coordinator?.start()
}
}
My Coordinator:
class AuthorizationCoordinator: RegistrationCoordinatorProtocol {
var presenter: PresenterProtocol?
var navigationController: UINavigationController
var childCoordinators: [CoordinatorProtocol] = []
//Init
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
private func showRegistrationViewController() {
let registrationViewController = RegistrationViewController()
registrationViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(registrationViewController, animated: true)
}
private func showLoginViewController() {
let loginViewController = LoginViewController()
loginViewController.view.backgroundColor = .orange
self.navigationController.pushViewController(loginViewController, animated: true)
}
}
Could you check if startViewController is pushed/presented or not?
func start() {
presenter = StartModulPresenter(navigationController: navigationController)
let startViewController = StartViewController(startModulPresenter: presenter as! StartModulPresenter)
startViewController.output = { [weak self] action in
switch action {
case .registrationButtonTapped:
self?.showRegistrationViewController()
case .loginButtonTapped:
self?.showLoginViewController()
}
}
}
And, is self.output is nil or not? If it is nil please check your assignment call, it needed to be called before you use this variable.
#objc func loginButtonPressed() {
startModulPresenter.openNextScreen()
self.output?(.loginButtonTapped)
}
Honestly, I don't recommend you to use this design pattern, just a simple thing but the real result is too complicated.
Just use protocol-based MVC. View communicate with Controller via protocol/closure or Reactive-based with Combine (PassthroughSubject/CurrentValueSubject)
I have an NSObject class that consists of a basic EKCalendarChooser implementation and I am unable to get the delegate functions calendarChooserDidFinish, calendarChooserSelectionDidChange, and calendarChooserDidCancel working. I'm not sure if it is the fact that everything is in an NSObject but I wanted to keep this code separate from my other files.
I've tried a lot of troubleshooting such as not keeping the delegate methods under an extension and even making a global variable for the EKCalendarChooser as I found this post which states that non-global items could be dereferenced in contexts like this. Overall I can get the controller to pop up and it's just the way I want, but the delegate methods don't work. Below is the entire code and in my main viewController I get this to show with AddAppointments(parentViewController: self).chooseCalendarTapped()
import UIKit
import EventKitUI
class Cal: NSObject {
let eventStore = EKEventStore()
var parentViewController: UIViewController
var CalendarChooser: EKCalendarChooser = EKCalendarChooser()
init(parentViewController: UIViewController) {
self.parentViewController = parentViewController
super.init()
}
func chooseCalendarTapped() {
let authStatus = EKEventStore.authorizationStatus(for: .event)
switch authStatus {
case .authorized:
showCalendarChooser()
case .notDetermined:
requestAccess()
case .denied:
// Explain to the user that they did not give permission
break
case .restricted:
break
#unknown default:
preconditionFailure("Who knows what the future holds 🤔")
}
}
func requestAccess() {
eventStore.requestAccess(to: .event) { (granted, error) in
if granted {
// may not be called on the main thread..
DispatchQueue.main.async {
self.showCalendarChooser()
}
}
}
}
func showCalendarChooser() {
CalendarChooser = EKCalendarChooser(selectionStyle: .single, displayStyle: .allCalendars, entityType: .event, eventStore: eventStore)
// customization
CalendarChooser.showsDoneButton = true
CalendarChooser.showsCancelButton = true
// dont forget the delegate
CalendarChooser.delegate = self
let nvc = UINavigationController(rootViewController: CalendarChooser)
parentViewController.present(nvc, animated: true, completion: nil)
}
}
extension Cal : EKCalendarChooserDelegate {
func calendarChooserDidFinish(_ calendarChooser: EKCalendarChooser) {
print(calendarChooser.selectedCalendars)
// dismiss(animated: true, completion: nil)
}
func calendarChooserSelectionDidChange(_ calendarChooser: EKCalendarChooser) {
print("Changed selection")
}
func calendarChooserDidCancel(_ calendarChooser: EKCalendarChooser) {
print("Cancel tapped")
// dismiss(animated: true, completion: nil)
}
}
Check if this works now:
Declaring a var at the controller class level works here instead of having a new instance inside a function:
Controller:
class HomeVC: UIViewController {
var event = EventManager()
override func viewDidLoad() {
super.viewDidLoad()
event.chooseCalendarTapped(presentingVC: self)
}
}
EventManager Class:
import UIKit
import EventKitUI
class EventManager: NSObject {
let eventStore = EKEventStore()
override init() {
super.init()
}
weak var delegate: EKCalendarChooserDelegate? = nil
var presentingVC: UIViewController? = nil
func chooseCalendarTapped(presentingVC: UIViewController) {
self.presentingVC = presentingVC
let authStatus = EKEventStore.authorizationStatus(for: .event)
switch authStatus {
case .authorized:
showCalendarChooser()
case .notDetermined:
requestAccess()
case .denied:
// Explain to the user that they did not give permission
break
case .restricted:
break
#unknown default:
preconditionFailure("Who knows what the future holds 🤔")
}
}
func requestAccess() {
eventStore.requestAccess(to: .event) { (granted, error) in
if granted {
// may not be called on the main thread..
DispatchQueue.main.async {
self.showCalendarChooser()
}
}
}
}
func showCalendarChooser() {
let vc = EKCalendarChooser(selectionStyle: .single, displayStyle: .allCalendars, entityType: .event, eventStore: eventStore)
// customization
vc.showsDoneButton = true
vc.showsCancelButton = true
// dont forget the delegate
vc.delegate = self
let nvc = UINavigationController(rootViewController: vc)
presentingVC?.present(nvc, animated: true, completion: nil)
}
}
extension EventManager: EKCalendarChooserDelegate {
func calendarChooserDidFinish(_ calendarChooser: EKCalendarChooser) {
print(calendarChooser.selectedCalendars)
presentingVC?.dismiss(animated: true, completion: nil)
}
func calendarChooserSelectionDidChange(_ calendarChooser: EKCalendarChooser) {
print("Changed selection")
}
func calendarChooserDidCancel(_ calendarChooser: EKCalendarChooser) {
print("Cancel tapped")
presentingVC?.dismiss(animated: true, completion: nil)
}
}
I have created a base class for Apple Pay functionality. But that base class is inherited by NSObject . I have created the payment authorise viewcontroller with in this class only and assigned it’s delegate to self. Now it’s not invoking the delegate methods of pkpaymentauthorizationviewcontrollerdelegate. How can I fix this situation and implement delegate method just once in base class and reuse it else where in application. Please help.
class ApplePay: NSObject {
var applePayItem: PKPaymentSummaryItem?
var baseVC: UIViewController?
let payNetworks = [PKPaymentNetwork.masterCard, .visa, .amex, .discover]
init(forItem: Product) {
applePayItem = PKPaymentSummaryItem.init(label: forItem.name ?? "",
amount: NSDecimalNumber(value: forItem.price ?? 0))
}
func initiatePayment(complete: #escaping (Bool) -> Void) {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: payNetworks) {
let request = PKPaymentRequest()
request.currencyCode = "USD"
request.countryCode = "US"
request.merchantIdentifier = <*identifier from my development account*>
request.merchantCapabilities = PKMerchantCapability.capability3DS
request.supportedNetworks = payNetworks
request.paymentSummaryItems = [applePayItem!]
guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else {
return
}
baseVC = UIApplication.shared.keyWindow?.rootViewController?.children.last
baseVC?.present(paymentVC, animated: true, completion: nil)
paymentVC.delegate = self
complete(true)
} else {
complete(false)
CaAssembly.resolve(CaAlertProtocol.self)!.showAlert(CaConstant.alertTitles.addCardApplePay, dismiss: {})
}
}
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
controller.dismiss {
}
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
}
extension ApplePay: PKPaymentAuthorizationViewControllerDelegate,
PKPaymentAuthorizationControllerDelegate {
#available(iOS 11.0, *)
func paymentAuthorizationViewController(_ controller:
PKPaymentAuthorizationViewController,
didAuthorizePayment payment: PKPayment,
handler completion: #escaping
(PKPaymentAuthorizationResult) -> Void) {
print(“PKPaymentAuthorizationViewController didAuthorizePayment” )
}
}
Just for the update ... I was able to implement it by UIViewController inherited base class ... using library [github.com/IcaliaLabs/Presentr] using which I presented base class as modal with dimensions (w:0,h:0) and initiated Apple Pay on its viewdidappear :
import UIKit
import PassKit
import Alamofire
import Presentr
struct Product {
var name: String
var price: Double
}
class ApplePay: UIViewController , PKPaymentAuthorizationControllerDelegate,
PKPaymentAuthorizationViewControllerDelegate{ //
var applePayItem: PKPaymentSummaryItem?
let output = ""
var baseVC: UIViewController?
let payNetworks = [PKPaymentNetwork.masterCard, .visa, .amex, .discover]
var presenter: Presentr = {
let customPresenter = Presentr(
presentationType: .custom(
width: ModalSize.custom(size: 0),
height: ModalSize.custom(size: 0),
center: ModalCenterPosition.bottomCenter
)
)
customPresenter.keyboardTranslationType = .compress
return customPresenter
}()
var forItem: Product?
override func viewDidLoad() {
applePayItem = PKPaymentSummaryItem.init(label: forItem?.name ?? "",
amount: NSDecimalNumber(value: forItem?.price ?? 0))
}
override func viewDidAppear(_ animated: Bool) {
initiatePayment()
}
func initiatePayment() {
if PKPaymentAuthorizationViewController.canMakePayments(usingNetworks: payNetworks) {
let request = PKPaymentRequest()
request.currencyCode = "USD"
request.countryCode = "US"
request.merchantIdentifier = <merchant id created on developer account>
request.merchantCapabilities = PKMerchantCapability.capability3DS
request.supportedNetworks = payNetworks
request.paymentSummaryItems = [applePayItem!]
guard let paymentVC = PKPaymentAuthorizationViewController(paymentRequest: request) else {
return
}
self.present(paymentVC, animated: true, completion: nil)
paymentVC.delegate = self
}
}
func paymentAuthorizationControllerDidFinish(_ controller: PKPaymentAuthorizationController) {
controller.dismiss {
}
}
func paymentAuthorizationViewControllerDidFinish(_ controller: PKPaymentAuthorizationViewController) {
controller.dismiss(animated: true, completion: nil)
}
#available(iOS 11.0, *)
func paymentAuthorizationViewController(_ controller: PKPaymentAuthorizationViewController, didAuthorizePayment payment: PKPayment, handler completion: #escaping (PKPaymentAuthorizationResult) -> Void) {
}
}
I would like to understand what would be the best way to implement a delegate out UIViewController class
How can I manage the delegate using controller: UIViewController parameter of my function in AuthManager?
These are the two classes I'm working with .. I show you small examples to make you understand
class StartController: UIViewController {
#objc private func presentAuthFacebookController() {
AuthManager.signInWithFacebook(controller: self)
}
}
class AuthManager {
static func signInWithFacebook(controller: UIViewController) {
let loginManager = LoginManager()
loginManager.logIn(permissions: [.publicProfile, .email], viewController: controller) { (result) in
switch result {
case .cancelled : print("\n AuthFacebook: operazione annullata dall'utente \n")
case .failed(let error) : print("\n AuthFacebook: \(error) \n")
case .success(granted: _, declined: let declinedPermission, token: _):
let authVC = ExistingEmailController()
authVC.delegate = // ?????? (controller)
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
}
}
}
I personally don't think StartController should know about/conform to ExistingEmailControllerDelegate. But if you really want, you can declare controller as a composition type:
static func signInWithFacebook(controller: UIViewController & ExistingEmailControllerDelegate) {
...
authVC.delegate = controller
In my opinion, the whole point of having a AuthManager is to create a layer of abstraction on top of ExistingEmailController, and to encapsulate the logic of authentication. Therefore, StartController shouldn't know, or care, about ExistingEmailControllerDelegate. It only knows about AuthManager.
AuthManager should be the delegate of ExistingEmailController, which implies that signInWithFacebook should not be static, and AuthManager can have an AuthManagerDelegate that StartController conforms to:
class AuthManager : ExistingEmailControllerDelegate {
weak var delegate: AuthManagerDelegate?
func signInWithFacebook(controller: UIViewController) {
...
let authVC = ExistingEmailController()
authVC.delegate = self
UIApplication.shared.windows.first?.rootViewController?.present(authVC, animated: true, completion: nil)
}
func someMethodFromExistingEmailControllerDelegate() {
delegate?.someMethod() // delegating it self.delegate, which StartController conforms to
}
}
protocol AuthManagerDelegate : class {
func someMethod()
}
class StartController: UIViewController, AuthManagerDelegate {
var authManager: AuthManager!
override func viewDidLoad() {
authManager = AuthManager()
authManager.delegate = self
}
#objc private func presentAuthFacebookController() {
authManager.signInWithFacebook(controller: self)
}
func someMethod() {
// write here the code that you would have written in someMethodFromExistingEmailControllerDelegate
}
}
I'm using the Spotify iOS SDK. When a user logs into Spotify using the app, on call back loginVC transitions to musicPlayerVC. But, when a user logs into the app using a web view, once the web view dismisses and the loginVC is shown, the musicPlayerVC is loaded (print statements from viewDidLoad occur), but loginVC does not dismiss and musicPlayerVC does not show.
loginVC:
class loginVC: UIViewController, SPTStoreControllerDelegate, WebViewControllerDelegate {
#IBOutlet weak var statusLabel: UILabel!
var authViewController: UIViewController?
var firstLoad: Bool!
var Information: [String:String]?
override func viewDidLoad() {
super.viewDidLoad()
// NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
self.statusLabel.text = ""
self.firstLoad = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(self.sessionUpdatedNotification), name: NSNotification.Name(rawValue: "sessionUpdated"), object: nil)
let auth = SPTAuth.defaultInstance()
// Uncomment to turn off native/SSO/flip-flop login flow
//auth.allowNativeLogin = NO;
// Check if we have a token at all
if auth!.session == nil {
self.statusLabel.text = ""
return
}
// Check if it's still valid
if auth!.session.isValid() && self.firstLoad {
// It's still valid, show the player.
print("View did load, still valid, showing player")
self.showPlayer()
return
}
// Oh noes, the token has expired, if we have a token refresh service set up, we'll call tat one.
self.statusLabel.text = "Token expired."
if auth!.hasTokenRefreshService {
self.renewTokenAndShowPlayer()
return
}
// Else, just show login dialog
}
override var prefersStatusBarHidden: Bool {
return true
}
func getAuthViewController(withURL url: URL) -> UIViewController {
let webView = WebViewController(url: url)
webView.delegate = self
return UINavigationController(rootViewController: webView)
}
func sessionUpdatedNotification(_ notification: Notification) {
self.statusLabel.text = ""
let auth = SPTAuth.defaultInstance()
self.presentedViewController?.dismiss(animated: true, completion: { _ in })
if auth!.session != nil && auth!.session.isValid() {
self.statusLabel.text = ""
print("Session updated, showing player")
self.showPlayer()
}
else {
self.statusLabel.text = "Login failed."
print("*** Failed to log in")
}
}
func showPlayer() {
self.firstLoad = false
self.statusLabel.text = "Logged in."
self.Information?["SpotifyUsername"] = SPTAuth.defaultInstance().session.canonicalUsername
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "ShowPlayer", sender: self)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowPlayer" {
if let destination = segue.destination as? PlayController {
destination.Information = self.Information
}
}
}
internal func productViewControllerDidFinish(_ viewController: SPTStoreViewController) {
self.statusLabel.text = "App Store Dismissed."
viewController.dismiss(animated: true, completion: { _ in })
}
func openLoginPage() {
self.statusLabel.text = "Logging in..."
let auth = SPTAuth.defaultInstance()
if SPTAuth.supportsApplicationAuthentication() {
self.open(url: auth!.spotifyAppAuthenticationURL())
} else {
self.authViewController = self.getAuthViewController(withURL: SPTAuth.defaultInstance().spotifyWebAuthenticationURL())
self.definesPresentationContext = true
self.present(self.authViewController!, animated: true, completion: { _ in })
}
}
func open(url: URL) {
if #available(iOS 10, *) {
UIApplication.shared.open(url, options: [:],
completionHandler: {
(success) in
print("Open \(url): \(success)")
})
} else {
let success = UIApplication.shared.openURL(url)
print("Open \(url): \(success)")
}
}
func renewTokenAndShowPlayer() {
self.statusLabel.text = "Refreshing token..."
SPTAuth.defaultInstance().renewSession(SPTAuth.defaultInstance().session) { error, session in
SPTAuth.defaultInstance().session = session
if error != nil {
self.statusLabel.text = "Refreshing token failed."
print("*** Error renewing session: \(error)")
return
}
self.showPlayer()
}
}
func webViewControllerDidFinish(_ controller: WebViewController) {
// User tapped the close button. Treat as auth error
}
}
webController :
import UIKit
import WebKit
#objc protocol WebViewControllerDelegate {
func webViewControllerDidFinish(_ controller: WebViewController)
/*! #abstract Invoked when the initial URL load is complete.
#param success YES if loading completed successfully, NO if loading failed.
#discussion This method is invoked when SFSafariViewController completes the loading of the URL that you pass
to its initializer. It is not invoked for any subsequent page loads in the same SFSafariViewController instance.
*/
#objc optional func webViewController(_ controller: WebViewController, didCompleteInitialLoad didLoadSuccessfully: Bool)
}
class WebViewController: UIViewController, UIWebViewDelegate {
var loadComplete: Bool = false
var initialURL: URL!
var webView: UIWebView!
var delegate: WebViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
print(initialURL)
let initialRequest = URLRequest(url: self.initialURL)
self.webView = UIWebView(frame: self.view.bounds)
self.webView.delegate = self
self.webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.view.addSubview(self.webView)
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.done))
self.webView.loadRequest(initialRequest)
}
func done() {
self.delegate?.webViewControllerDidFinish(self)
self.presentingViewController?.dismiss(animated: true, completion: { _ in })
}
func webViewDidFinishLoad(_ webView: UIWebView) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error) {
if !self.loadComplete {
delegate?.webViewController?(self, didCompleteInitialLoad: true)
self.loadComplete = true
}
}
init(url URL: URL) {
super.init(nibName: nil, bundle: nil)
self.initialURL = URL as URL!
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}