EXC_BAD_ACCESS in release but not debug - swift

My product will run fine in debug from xcode to both a simulator and device, but did not run when archived and submitted adhoc. I switched my scheme to run as release, and sure enough, it crashes.
The below portion of code generates an EXC_BAD_ACCESS fault on the var homeNav line. This method is in a custom TabBar Controller. I've also included the app delegate call below, as this is where the tabcontroller is created and displayed.
Tab Controller Call:
//Sets up view on load
override func viewDidLoad() {
super.viewDidLoad()
//Setup and add home view
var homeNav = CMSSNavigationController(navigationBarClass:CMSSNavigationBar.self, toolbarClass:nil)
homeNav.viewControllers.append(CMSSHomeController(nibName:XIBS.VIEW_HOME, bundle:nil))
homeNav.tabBarItem.image = UIImage(named:Images.TAB_HOME)
homeNav.edgesForExtendedLayout = UIRectEdge.None
//Setup and add profile view
var profileNav = CMSSNavigationController(navigationBarClass:CMSSNavigationBar.self, toolbarClass:nil)
profileNav.viewControllers.append(CMSSProfileController(nibName:XIBS.VIEW_PROFILE, bundle:nil))
profileNav.tabBarItem.image = UIImage(named:Images.TAB_PROFILE)
profileNav.edgesForExtendedLayout = UIRectEdge.None
//Set tabs
self.viewControllers = NSArray(objects: homeNav, profileNav)
}
App Delegate call:
//Handles events after launch
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//Setup Google Analytics
GAI.sharedInstance().trackUncaughtExceptions = true
GAI.sharedInstance().dispatchInterval = 20
//TODO: Set logging level for analytics
GAI.sharedInstance().trackerWithTrackingId(GoogleAnalytics.TRACKING_ID)
//Set Window
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
//Create root tab controller
var rootControl = CMSSTabBarController()
self.window?.rootViewController = rootControl
self.window?.makeKeyAndVisible();
return true
}
The code runs fine in release if all UINavigationControllers are declared as CMSSNavigationController() without providing a CMSSNavigationBar.
Why would something like this occur in release, but not debug? I see nowhere that would indicate an object or reference is not allocated correctly here.

The issue was that the toolbar class in the constructor of a UINavigationController cannot be nil. However, the compiler will not complain about this. And, although I cannot say why, the debug will allow it and run fine, but the release will not.
using the line
var homeNav = CMSSNavigationController(navigationBarClass:CMSSNavigationBar.self, toolbarClass:UIToolbar.self)
fixes the issue entirely.

Related

iOS 14 display status bar in landscape

Since iOS 8, status bar is hidden in landscape by default, I want to make it visible. I created a new empty project and tried several things from other topics but none of these work:
First solution I tried (I also added the UIViewControllerBasedStatusBarAppearance key to the Info.Plist and tested with it set either to true or false):
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setNeedsStatusBarAppearanceUpdate()
}
override var prefersStatusBarHidden: Bool {
return false
}
}
Second solution I tried was to use the deprecated method setStatusBarHidden in the App Delegate
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
application.setStatusBarHidden(false, with: .none)
// Override point for customization after application launch.
return true
}
In both cases status bar does not appear in landscape. Is there any way to see it ?
Your code is correct (this is all you need in a new vanilla iOS app project):
override var prefersStatusBarHidden: Bool {
return false
}
The problem is that this works only up through iOS 12. In iOS 13 and later, the runtime stopped interpreting a value of false to allow you to override the hiding of the status bar in landscape on an iPhone. Similarly, calling application.setStatusBarHidden fails to show the status in landscape.
This is probably a side effect of the UIWindowScene architecture that was introduced in iOS 13. Or it might be intended to make all iPhone models behave the same way (with or without bezel).

Performing a completion handler before app launches

I am attempting to open an app in one of two ways:
If the user has no UserDefaults saved, then open up a WelcomeViewController
If the user has UserDefaults saved, then open up a MenuContainerViewController as a home page
In step 2, if there are UserDefaults saved, then I need to log a user in using Firebase which I have through a function with a completion handler. If step 2 is the case, I want to open MenuContainerViewController within the completion block without any UI hiccups.
Here is the code I have currently:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
FirebaseApp.configure()
guard
let email = UserDefaults.standard.string(forKey: "email"),
let password = UserDefaults.standard.string(forKey: "password")
else {
// User has no defaults, open welcome screen
let welcomeViewController = WelcomeViewController()
self.window?.rootViewController = welcomeViewController
self.window?.makeKeyAndVisible()
return true
}
// User has defaults saved locally, open home screen of app
let authentificator = Authentificator()
authentificator.login(with: email, password) { result, _ in
if result {
let menuContainerViewController = MenuContainerViewController()
self.window?.rootViewController = menuContainerViewController
self.window?.makeKeyAndVisible()
}
}
return true
}
Here is a video of the current UI, when I need to run the completion handler, the transition is not smooth into the app (there is a brief second with a black screen).
Please help me figure out how to make a smooth app launch.
I've had to handle situations similarly in my Firebase applications. What I typically do is make an InitialViewController. This is the view controller that is always loaded, no matter what. This view controller is initially set up to seamlessly look exactly like the launch screen.
This is what the InitialViewController looks like in the interface builder:
And this is what my launch screen looks like:
So when I say they look exactly the same, I mean they look exactly the same. The sole purpose of this InitialViewController is to handle this asynchronous check and decide what to do next, all while looking like the launch screen. You may even copy/paste interface builder elements between the two view controllers.
So, within this InitialViewController, you make the authentication check in viewDidAppear(). If the user is logged in, we perform a segue to the home view controller. If not, we animate the user onboarding elements into place. The gifs demonstrating what I mean are pretty large (dimension-wise and data-wise), so they may take some time to load. You can find each one below:
User previously logged in.
User not previously logged in.
This is how I perform the check within InitialViewController:
#IBOutlet var loginButton: UIButton!
#IBOutlet var signupButton: UIButton!
#IBOutlet var stackView: UIStackView!
#IBOutlet var stackViewVerticalCenterConstraint: NSLayoutConstraint!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
//When the view appears, we want to check to see if the user is logged in.
//Remember, the interface builder is set up so that this view controller **initially** looks identical to the launch screen
//This gives the effect that the authentication check is occurring before the app actually finishes launching
checkLoginStatus()
}
func checkLoginStatus() {
//If the user was previously logged in, go ahead and segue to the main app without making them login again
guard
let email = UserDefaults.standard.string(forKey: "email"),
let password = UserDefaults.standard.string(forKey: "password")
else {
// User has no defaults, animate onboarding elements into place
presentElements()
return
}
let authentificator = Authentificator()
authentificator.login(with: email, password) { result, _ in
if result {
//User is authenticated, perform the segue to the first view controller you want the user to see when they are logged in
self.performSegue(withIdentifier: "SkipLogin", sender: self)
}
}
}
func presentElements() {
//This is the part where the illusion comes into play
//The storyboard elements, like the login and signup buttons were always here, they were just hidden
//Now, we are going to animate the onboarding UI elements into place
//If this function is never called, then the user will be unaware that the launchscreen was even replaced with this view controller that handles the authentication check for us
//Make buttons visible, but...
loginButton.isHidden = false
signupButton.isHidden = false
//...set their alpha to 0
loginButton.alpha = 0
signupButton.alpha = 0
//Calculate distance to slide up
//(stackView is the stack view that holds our elements like loginButton and signupButton. It is invisible, but it contains these buttons.)
//(stackViewVerticalCenterConstraint is the NSLayoutConstraint that determines our stackView's vertical position)
self.stackViewVerticalCenterConstraint.constant = (view.frame.height / 2) + (stackView.frame.height / 2)
//After half a second, we are going to animate the UI elements into place
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIView.animate(withDuration: 0.75) {
self.loginButton.alpha = 1
self.signupButton.alpha = 1
//Create correct vertical position for stackView
self.stackViewVerticalCenterConstraint.constant = (self.view.frame.height - self.navigationController!.navigationBar.frame.size.height - self.signupButton.frame.maxY - (self.stackView.frame.size.height / 2)) / 3
self.view.layoutIfNeeded()
}
}
}

Google Places Autocomplete Delegate not set

I am setting the delegate of the autocomplete view controller to the current class, which has an autocompleteviewcontrollerdelegate extension. This has has always worked in the past, but recently I am getting an error message printing out that says the delegate for the autocomplete controller is not set and I can confirm the delegate is no longer being called. I have the print(self) line to check that I am in fact setting the correct class to be the delegate. I have this exact same setup in another part of my app that is working fine. Any suggestions on how to debug this issue?
#objc func searchButton() {
print(self)
let acController = GMSAutocompleteViewController()
acController.delegate = self
acController.autocompleteBounds = getBounds(latitude: latitude!, longitude: longitude!)
// Specify a filter.
//let filter = GMSAutocompleteFilter()
//filter.type = establishment
//acController.autocompleteFilter = filter
present(acController, animated: true, completion: nil)
}
In my viewwillDisappear method I pop the view controller so that if the user navigated away from the page with the tab bar controller the next time they navigate to the page they will be on the main page. Whenever the autocomplete view controller was appearing the parent viewcontroller was getting popped off the stack and killing off the delegate.

Swift - TabViewController with SwipeViewControler in one of its tabs

Another newbie to swift here. I want my application to have four tabs on the bottom and inside one of the tabs I want to be able to swipe between two 'pager' style tabs.
I have been able to make the four tabs work at the bottom by starting a new tabbed app and adding two additional view controllers.
Then I tried adding a SwipeViewController (https://github.com/fortmarek/SwipeViewController) to one of the tabs and it worked but it overrode the entry point so all I see is the two SwipeView tabs without the TabController tabs on the bottom.
!I want this inside of the Home tab]1
I think the problem is in my delegate code as it is setting self.window?.rootViewController to the SwipViewController. Getting rid of that assignment makes the Tab Bar show back up, but it is black inside of the Home tab so it is still not working.
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
let navigationController = SecondViewController(rootViewController: pageController)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
return true
}
I also tried getting the corresponding window for the "Home" tab from the storyboard and setting the navigation controller to that.
let secondViewControllerWindow = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Home").view.window
secondViewControllerWindow?.rootViewController = navigationController
secondViewControllerWindow?.makeKeyAndVisible()
But Home is still empty. Here is the ViewController code that is represented by the Home tab and should control the SwipViewController
import UIKit
class SecondViewController: SwipeViewController {
override func viewDidLoad() {
super.viewDidLoad()
let stb = UIStoryboard(name: "Main", bundle: nil)
let page_one = stb.instantiateViewController(withIdentifier: "nba") as UIViewController
let page_two = stb.instantiateViewController(withIdentifier: "mlb") as UIViewController
setViewControllerArray([page_one, page_two])
page_one.title = "NBA"
page_two.title = "MLB"
setFirstViewController(0)
equalSpaces = true
//setButtons(UIFont.systemFontOfSize(18), color: UIColor.blackColor())
//setSelectionBar(80, height: 3, color: UIColor.blackColor())
setButtonsOffset(40, bottomOffset: 5)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
My questions is similar to these other questions but I did not find an answer.
Using Page View Controller inside Tab Swift
Xcode 6 - Swift - Custom Tabbar with Navigation
I faced the same problem recently, and I found the solution here.
https://github.com/fortmarek/SwipeViewController/issues/26
I didn't put the code in didFinishLaunchingWithOptions.
I tried to work on main.storyboard.
I dragged a PageViewController in and set it as your(SecondViewController)'s RootView.
It worked for me.

How does the SignIn screen get called in Cannonball for iOS?

I'm new to Digits for iOS. I got the authentication part working, and now I'm trying to integrate the sign-in with the rest of my app.
One thing that really puzzles me is that in Cannonball, the Initial View Controller is the main screen. However, when running Cannonball, the first screen I see is the Sign In screen.
I don't see any segue from the Navigation Controller to the Sign In. Also, I've looked at the code in the Theme Chooser View Controller, and don't see any reference to the Sign In's View Controller.
So, how does the Sign In screen actually get executed? Where is that logic happening? Thanks.
The answer is that the AppDelegate.swift file does the logic. The code below were copy/pasted from Cannonball's. I went ahead and added comments (some came with Cannonball).
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool {
// Developers: Welcome! Get started with Fabric.app.
let welcome = "Welcome to Cannonball! Please onboard with the Fabric Mac app. Check the instructions in the README file."
assert(NSBundle.mainBundle().objectForInfoDictionaryKey("Fabric") != nil, welcome)
// Register Crashlytics, Twitter, Digits and MoPub with Fabric.
//Fabric.with([Crashlytics(), Twitter(), Digits(), MoPub()])
Fabric.with([Digits()])
// Check for an existing Twitter or Digits session before presenting the sign in screen.
// Detect if the Digits session is nil
// if true, set the root view controller to the Sign In's view controller
if Twitter.sharedInstance().session() == nil && Digits.sharedInstance().session() == nil {
//Main corresponds to Main.storyboard
let storyboard = UIStoryboard(name: "Main", bundle: nil)
//Sign In's View Controller
let signInViewController: AnyObject! = storyboard.instantiateViewControllerWithIdentifier("SignInViewController")
//Set the UIWindow's ViewController to the SignIn's ViewController
window?.rootViewController = signInViewController as? UIViewController
}
return true
}