How to refresh UITabBar after changing viewControllers - ios

I'm implementing a user authentication system for an app. If the user is logged in they get one set of available tabs to select. If They're not logged in they get another. Now the issue I'm running into is that after a user logs in (app redirects to safari to some oauth stuff and then returns to the app), I update the tabs from the UITabBarController like so:
private var accessibleViewControllers = [UIViewController]()
func setUpView() {
var viewControllersToSet = [UIViewController]()
if let user = theUser {
for controller in accessibleViewControllers {
if !(controller is LogInViewController) {
viewControllersToSet.append(controller)
}
}
} else {
for controller in accessibleViewControllers {
if controller is LogInViewController || controller is HomeNavigator {
viewControllersToSet.append(controller)
}
}
}
setViewControllers(viewControllersToSet, animated: false)
}
Now the funny thing is, the tab icons don't refresh, but I can still click on the spaces where the new icons would be to link through to the associated view. How do I refresh the tabs so that the right icons appear?

This was a threading issue. I was loading the user data over a background thread and then calling the delegation method setUpView from the same background thread. To fix this I ran it back on the main queue:
dispatch_async(dispatch_get_main_queue()) {
if self.accessibleViewControllers != nil {
var viewControllersToSet = [UIViewController]()
if let user = MediatedUser.shared {
for controller in self.accessibleViewControllers! {
if !(controller is LogInViewController) {
viewControllersToSet.append(controller)
}
}
} else {
for controller in self.accessibleViewControllers! {
if controller is LogInViewController || controller is HomeNavigator {
viewControllersToSet.append(controller)
}
}
}
self.viewControllers = viewControllersToSet
}
}

Related

home page jump over login feature in Swift

I am pretty new to the swift, I am trying to implement the feature that when the user logged in, they will get directly to the home page rather than the login page every time they reopen the app.
I took the reference to the tutorial: https://www.youtube.com/watch?v=gjYAIXjpIS8&t=146s. and I implemented the is logged in boolean checking as he did, but I somehow encounter the trouble reopen the homepage while logged in. I have an error message:[Presentation] Attempt to present <UITabBarController: 0x7fa68102ea00> on <IFTTT.ViewController: 0x7fa67fe0c150> (from <IFTTT.ViewController: 0x7fa67fe0c150>) whose view is not in the window hierarchy.
This is how my login page controller class:(which is the entry point when opening the app) I tried present as the tutorial and performsegue and both shows up the same error message above
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if isLoggedIn() {
performSegue(withIdentifier: "logInJump", sender: nil)
}
}
fileprivate func isLoggedIn() -> Bool {
print("logged in status: \(UserDefaults.standard.bool(forKey: "isLoggedIn"))")
return UserDefaults.standard.bool(forKey: "isLoggedIn")
}
#IBAction func signInButton(_ sender: Any) {
print("sign in tapped")
if let appURL = URL(string: "http://vocation.cs.umd.edu/flask/register") {
UIApplication.shared.open(appURL) { success in
if success {
print("The URL was delivered successfully.")
} else {
print("The URL failed to open.")
}
}
} else {
print("Invalid URL specified.")
}
}
}
// button broder width
#IBDesignable extension UIButton {
#IBInspectable var borderWidth: CGFloat {
set {
layer.borderWidth = newValue
}
get {
return layer.borderWidth
}
}
#IBInspectable var cornerRadius: CGFloat {
set {
layer.cornerRadius = newValue
}
get {
return layer.cornerRadius
}
}
#IBInspectable var borderColor: UIColor? {
set {
guard let uiColor = newValue else { return }
layer.borderColor = uiColor.cgColor
}
get {
guard let color = layer.borderColor else { return nil }
return UIColor(cgColor: color)
}
}
}
and this is my messy story board and segues :
story board
I tried adding a navigation controller that the app entry point gets in there and it performs the isLoggedIn the same as the view controller class did, but it also has the same error.
Can someone walk me through how to fix it or any other better techniques? I felt like I am blind since I just get into the study of swift. Thank you!
You need to thread your request to perform the segue. Because your calling performSegue in the viewDidLoad() what happens is that your call is being called before everything is loaded, so you need to introduce some lag.
Threads are sometimes called lightweight processes because they have
their own stack but can access shared data. Because threads share the
same address space as the process and other threads within the
process, the operational cost of communication between the threads is
low, which is an advantage
An asynchronous function will await the execution of a promise, and an
asynchronous function will always return a promise. The promise
returned by an asynchronous function will resolve with whatever value
is returned by the function
Long story short, you need to wait for everything to be loaded into memory and if you're calling functions from the main stack/thread e.g. viewDidLoad() then there is a good chance that it hasn't been loaded into memory yet. Meaning, logInJump segue doesn't exist at that point in that view controller, thus your error.
The other possibility is you don't have the right view/segue ID but that should've thrown a different error.
Also, change the sender from nil to self. Actually this isn't necessary but I've always used self over nil
// change to desired number of seconds should be higher then 0
let when = DispatchTime.now() + 0.2
if isLoggedIn() {
DispatchQueue.main.asyncAfter(deadline: when) {
self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "logInJump", sender: nil)
}
}

how can I handle properly the behavior of camera in my Swift app?

In my swift app I'm allowing users to take photos.
For that purpose I've decided to use CameraManager from here: https://github.com/imaginary-cloud/CameraManager
When user opens my app, he sees a button - when he presses it, the camera view appears and he can take a photo. He also can dismiss the camera view and later on, at some point, press the button one more time to open camera view again.
If I understand it correctly from the plugin docs, I need to add a camera view to my view during first usage, then - in case of dismiss - invoke stopCaptureSession(), and during every next usage call resumeCaptureSession().
Currently in my swift code I have three methods:
let cameraManager = CameraManager()
fileprivate func addCameraToView()
{
cameraManager.addPreviewLayerToView(cameraView, newCameraOutputMode: CameraOutputMode.stillImage)
}
fileprivate func stopCaptureSession() {
cameraManager.stopCaptureSession()
}
fileprivate func resumeCaptreSession() {
cameraManager.resumeCaptureSession()
}
The IBAction for the button has the following code:
let currentCameraState = cameraManager.currentCameraStatus()
if currentCameraState == .notDetermined {
cameraManager.askUserForCameraPermission({ permissionGranted in
if permissionGranted {
self.resumeCaptreSession()
}
})
} else if (currentCameraState == .ready) {
self.resumeCaptreSession()
} else {
print("we do not have access to camera")
}
and in the IBAction for the dismiss button I had:
print("cancelling camera")
stopCaptureSession()
To make it work properly, I need to call addCameraToView() somewhere earlier - until now I was adding it in viewDidLoad, but I realized that I cannot do that because while doing so - the camera stays active until user presses the dismiss button.
So I thought about changing my code in IBAction for the camera button and add a camera from there. However, I have to add it only in case it wasn't add before - in the other case I need to call resumeCaptureSession().
The problem is that in CameraManager the function responsible for adding camera to the view is declared like this:
open func addPreviewLayerToView(_ view: UIView, newCameraOutputMode: CameraOutputMode) -> CameraState {
return addLayerPreviewToView(view, newCameraOutputMode: newCameraOutputMode, completion: nil)
}
open func addLayerPreviewToView(_ view: UIView, newCameraOutputMode: CameraOutputMode, completion: ((Void) -> Void)?) -> CameraState {
if _canLoadCamera() {
if let _ = embeddingView {
if let validPreviewLayer = previewLayer {
validPreviewLayer.removeFromSuperlayer()
}
}
if cameraIsSetup {
_addPreviewLayerToView(view)
cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
} else {
_setupCamera({ Void -> Void in
self._addPreviewLayerToView(view)
self.cameraOutputMode = newCameraOutputMode
if let validCompletion = completion {
validCompletion()
}
})
}
}
return _checkIfCameraIsAvailable()
}
and resumeCaptureSession() is defined like this:
open func resumeCaptureSession() {
if let validCaptureSession = captureSession {
if !validCaptureSession.isRunning && cameraIsSetup {
validCaptureSession.startRunning()
_startFollowingDeviceOrientation()
}
} else {
if _canLoadCamera() {
if cameraIsSetup {
stopAndRemoveCaptureSession()
}
_setupCamera({Void -> Void in
if let validEmbeddingView = self.embeddingView {
self._addPreviewLayerToView(validEmbeddingView)
}
self._startFollowingDeviceOrientation()
})
}
}
}
So my question is - when user opens camera view, how can I check if camera was added to the view before, and if it was added - call resumeCaptureSession(), otherwise do not call it and just leave it with calling addCameraToView?

In TabBarViewController, how can I access my children directly?

I want to access a child called SubscriptionsViewController (3rd tab)
This is what I'm doing, but it doesn't work.
var subscriptionsViewController: SubscriptionsViewController? {
get {
let viewControllers = self.childViewControllers
for viewController in viewControllers {
if let vc = viewController as? SubscriptionsViewController {
return vc
}
}
return nil
}
}
Assuming you have an instance of tab bar controller, you can do it as follows:
var subscriptionsViewController: SubscriptionsViewController? {
get {
let viewControllers = tabController.viewControllers //assuming you have a property tabBarController
for viewController in viewControllers {
if viewController is SubscriptionsViewController {
return vc
}
}
return nil
}
}
You can access a child of your tab bar controller with the following :
self.tabBarController.viewControllers[2]

Present GameCenter authenticationVC again

I'm facing a little issue here and I hope someone will help me figure out what is wrong.
*The test project presented below can be find here : http://goo.gl/wz84aA (FR) or https://goo.gl/0m8LrZ (Mega.NZ) *
I'm trying to present to the user the authentification view controller proposed by apple for the GameCenter feature. More precisely, re-present it if he canceled it on the first time.
I have a game with a storyboard like that :
GameNavigationController :
class GameNavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("showAuthenticationViewController"), name: PresentAuthenticationViewController, object: nil)
GameKitHelper.sharedInstance.authenticateLocalPlayer()
}
func showAuthenticationViewController() {
let gameKitHelper = GameKitHelper.sharedInstance
if let authenticationViewController = gameKitHelper.authenticationViewController {
self.topViewController.presentViewController(authenticationViewController, animated: true, completion: nil)
}
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}
}
MenuViewController :
class MenuViewController: UIViewController {
#IBAction func didTapLeaderboardBTN() {
// TRY 2
//if ( !GameKitHelper.sharedInstance.gameCenterEnabled) {
GameKitHelper.sharedInstance.authenticateLocalPlayer()
//} else {
GameKitHelper.sharedInstance.showGKGameCenterViewController(self)
//}
}
}
GameKitHelper :
import GameKit
import Foundation
let PresentAuthenticationViewController = "PresentAuthenticationViewController"
let singleton = GameKitHelper()
class GameKitHelper: NSObject, GKGameCenterControllerDelegate {
var authenticationViewController: UIViewController?
var lastError: NSError?
var gameCenterEnabled: Bool
class var sharedInstance: GameKitHelper {
return singleton
}
override init() {
gameCenterEnabled = true
super.init()
}
func authenticateLocalPlayer () {
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = { (viewController, error) in
self.lastError = error
if viewController != nil {
self.authenticationViewController = viewController
NSNotificationCenter.defaultCenter().postNotificationName(PresentAuthenticationViewController, object: self)
} else if localPlayer.authenticated {
self.gameCenterEnabled = true
} else {
self.gameCenterEnabled = false
}
}
}
func showGKGameCenterViewController(viewController: UIViewController!) {
if ( !self.gameCenterEnabled ) {
println("Local player is not authenticated")
// TRY 1
//self.authenticateLocalPlayer()
return
}
let gameCenterViewController = GKGameCenterViewController()
gameCenterViewController.gameCenterDelegate = self
gameCenterViewController.viewState = .Leaderboards
viewController.presentViewController(gameCenterViewController, animated: true, completion: nil)
}
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController!) {
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
}
What is currently working :
if the user is previously logged-in (within the GameCenter app) then he's able to open the leaderboard view ;
if the user wasn't logged-in then he's prompted to log-in when the game navigation controller is loaded (and then open the leaderboard).
What is NOT currently working :
if he cancel three time the authentification, then the authentification won't appear anymore (even at launch) ; // Apparently a known "problem", not "fixable"
if the user cancel his authentification, when he tries to load the leaderboard the authentification won't appear again.
I tried 2-3 things as you can see in the commented code above, but none of them is working ; I can't make the authentification view appear again.
PS : My code is written in Swift, but help in Objective-C is welcomed as well.
As you have found out, if the Game Center authentication dialog is canceled 3 times, then you can't bring it back without resetting the device.
There is another "security feature" built into Game Center which does not allow an app to re-authenticate if the user has already canceled the dialog without leaving the app. So for your authentication dialog to show, the user must leave and then re-enter your app.
There is really no way around it. What I've done in a couple of projects is to display a message to the user:
Game Center not available. Please make sure you are signed in through the Game Center app
I will show that message after trying to authenticate and if Game Center isn't available or the user is not signed in.
If you want to be able to re-present this to your user then go to settings -> General -> Reset -> -> Reset Location & Privacy.
This will force iOS to forget preferences for apps for example whether they can use location services, send you push notifications and also game centre preferences. Keep in mind this will reset privacy settings for all apps.

iOS: Show login viewcontroller or "another" viewcontroller inside of UITabbarController?

I have a tabbar with 5 tabs.
3 of these tabs requires authentication by having an account.
I know one solution is to present a modal viewcontroller when pressing one of these tabs.
I wish to present the login viewcontroller inside of the tabs instead of showing it modally. How can this be done and how can I "reload" the tabbar with the other viewcontrollers once a user has logged in?
I would do this by creating a subclass of UINavigationController which receives a UIViewController to show if user is logged in, and shows the login page in the other case.
class CustomNavController:UINavigationViewController {
let loggedInViewController:UIViewController
init(loggedInVC:UIViewController) {
loggedInViewController = loggedInVC
if (userLoggedIn) {
onLogin()
} else {
onLogout()
}
//setup listeners for authentication
super.init()
}
onLogout () {
self.viewControllers = [AuthenticationVC()]
}
onLogin () {
self.viewControllers = [loggedInViewController]
}
}
//code for setting up your UITabBarViewController
class MyTabbar:UITabBarViewController {
init() {
viewControllers = [
FirstVC(),
SecondVC(),
CustomNavController(ThirdVC()),
CustomNavController(ForthVC()),
CustomNavController(FifthVC())
]
}
}

Resources