How to transfer a boolean from app delegate to a viewcontroller? - swift

I'm currently trying to transfer a variable from app delegate to a ViewController. Most of the guides I find are in objective-c and I don't have the skill to convert that code into swift. Could someone help me find a way to do this.
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
var isIpad: Bool!
if UIDevice.current.userInterfaceIdiom == .pad {
isIpad = true
print("iPad")
}else{
print("not iPad")
isIpad = false
}
}

Although I would suggest to take a look to #rmaddy's first comment, you could declare a stored property anywhere in the module (it doesn't has to be declared in the app delegate):
let isIpad = UIDevice.current.userInterfaceIdiom == .pad
It means that there is no need to pass a value from the app delegate to the view controller. Thus in the view controller -for example-:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if isIpad {
// ...
}
}
}

Related

Swift calling a ViewController function from the AppDelegate [duplicate]

I am building an iOS app using the new language Swift. Now it is an HTML5 app, that displays HTML content using the UIWebView. The app has local notifications, and what i want to do is trigger a specific javascript method in the UIWebView when the app enters foreground by clicking (touching) the local notification.
I have had a look at this question, but it does not seem to solve my problem. I have also come across this question which tells me about using UIApplicationState, which is good as that would help me know the the app enters foreground from a notification. But when the app resumes and how do i invoke a method in the viewController of the view that gets displayed when the app resumes?
What i would like to do is get an instance of my ViewController and set a property in it to true. Something as follows
class FirstViewController: UIViewController,UIWebViewDelegate {
var execute:Bool = false;
#IBOutlet var tasksView: UIWebView!
}
And in my AppDelegate i have the method
func applicationWillEnterForeground(application: UIApplication!) {
let viewController = self.window!.rootViewController;
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
var setViewController = mainStoryboard.instantiateViewControllerWithIdentifier("FirstView") as FirstViewController
setViewController.execute = true;
}
so what i would like to do is when the app enters foreground again, i want to look at the execute variable and run the method as follows,
if execute{
tasksView.stringByEvaluatingJavaScriptFromString("document.getElementById('sample').click()");
}
Where should i put the code for the logic to trigger the javascript from the webview? would it be on viewDidLoad method, or one of the webView delegate methods? i have tried to put that code in the viewDidLoad method but the value of the boolean execute is set to its initial value and not the value set in the delegate when the app enters foreground.
If I want a view controller to be notified when the app is brought back to the foreground, I might just register for the UIApplication.willEnterForegroundNotification notification (bypassing the app delegate method entirely):
class ViewController: UIViewController {
private var observer: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
observer = NotificationCenter.default.addObserver(forName: UIApplication.willEnterForegroundNotification, object: nil, queue: .main) { [unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}
}
deinit {
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Note, in the completion closure, I include [unowned self] to avoid strong reference cycle that prevents the view controller from being deallocated if you happen to reference self inside the block (which you presumably will need to do if you're going to be updating a class variable or do practically anything interesting).
Also note that I remove the observer even though a casual reading of the removeObserver documentation might lead one to conclude is unnecessary:
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method.
But, when using this block-based rendition, you really do need to remove the notification center observer. As the documentation for addObserver(forName:object:queue:using:) says:
To unregister observations, you pass the object returned by this method to removeObserver(_:). You must invoke removeObserver(_:) or removeObserver(_:name:object:) before any object specified by addObserver(forName:object:queue:using:) is deallocated.
I like to use the Publisher initializer of NotificationCenter. Using that you can subscribe to any NSNotification using Combine.
import UIKit
import Combine
class MyFunkyViewController: UIViewController {
/// The cancel bag containing all the subscriptions.
private var cancelBag: Set<AnyCancellable> = []
override func viewDidLoad() {
super.viewDidLoad()
addSubscribers()
}
/// Adds all the subscribers.
private func addSubscribers() {
NotificationCenter
.Publisher(center: .default,
name: UIApplication.willEnterForegroundNotification)
.sink { [weak self] _ in
self?.doSomething()
}
.store(in: &cancelBag)
}
/// Called when entering foreground.
private func doSomething() {
print("Hello foreground!")
}
}
Add Below Code in ViewController
override func viewDidLoad() {
super.viewDidLoad()
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector:#selector(appMovedToForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
}
#objc func appMovedToForeground() {
print("App moved to foreground!")
}
In Swift 3, it replaces and generates the following.
override func viewDidLoad() {
super.viewDidLoad()
foregroundNotification = NotificationCenter.default.addObserver(forName:
NSNotification.Name.UIApplicationWillEnterForeground, object: nil, queue: OperationQueue.main) {
[unowned self] notification in
// do whatever you want when the app is brought back to the foreground
}

Unable to lock rotation for one view controller in IOS10

Background
I have an app that uses AVFoundation in order to have a custom camera. This happens in the OCRViewController. When I take a picture I send the captured picture to a different view ImagePreviewViewController.
I am using Xcode 10.2.1 (10E1001) with Swift 5
The Goal
What I would like to achieve is to lock the orientation of the ImagePreviewViewController to the original orientation of the image. I already know how to get the orientation of the image but I am not able to lock the orientation of the view.
I get the image rotation as such: let imageOri = capturedImage?.imageOrientation
What did I try?
I tried the accepted answers at and several other sources:
How to lock orientation just for one view controller?
How to lock orientation of one view controller to portrait mode only in Swift
https://www.hackingwithswift.com/example-code/uikit/how-to-lock-a-view-controllers-orientation-using-supportedinterfaceorientations
Reading the documentation at https://developer.apple.com/documentation/uikit/uiviewcontroller#//apple_ref/occ/instm/UIViewController/supportedInterfaceOrientations under Handling View Rotation the following is stated:
I also tried the many suggested solutions while writing this query, however, the majority appears to use the following approach (or a variation of it), and it does not work for me.
override func supportedInterfaceOrientations() -> Int {
return Int(UIInterfaceOrientationMask.Portrait.rawValue)
}
override func shouldAutorotate() -> Bool{
return false
}
override func preferredInterfaceOrientationForPresentation() -> UIInterfaceOrientation {
return UIInterfaceOrientation.Portrait
}
As of iOS 8, all rotation-related methods are deprecated. Instead, rotations are treated as a change in the size of the view controller’s view and are therefore reported using the viewWillTransition(to:with:) method.
However, I am not sure how to progress from here.
Interesting code snippets
The following method is in my OCRViewController, here I instantiate the ImagePreviewViewController and attach the captured image.
func displayCapturedPhoto(capturedPhoto : UIImage) {
let imagePreviewViewController = storyboard?.instantiateViewController(withIdentifier: "ImagePreviewViewController") as! ImagePreviewViewController
imagePreviewViewController.capturedImage = capturedPhoto
navigationController?.pushViewController(imagePreviewViewController, animated: true)
}
Using the below override function inside my ImagePreviewViewController I am able to detect the orientation of the view controller.
override func viewDidAppear(_ animated: Bool) {
if UIDevice.current.orientation.isLandscape {
print("Landscape")
} else {
print("Portrait")
}
}
To restrict the rotation of one screen, use this.
In AppDelegate
var restrictRotation = Bool()
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if !restrictRotation {
return .portrait
} else {
return .all
}
}
In your viewcontroller add the function,
func restrictRotation(restrict : Bool) -> Void {
let appDelegate = UIApplication.shared.delegate as? AppDelegate
appDelegate?.restrictRotation = restrict
}
In the ViewDidload() method, call the function to disable rotation.
self.restrictRotation(restrict: false)
in viewWillDisappear() method, call the function to enable rotation.
self.restrictRotation(restrict: true)

Creating an observer to check if MediaPlayer playbackState is paused or not

I have a music app and I wish to determine if playback has been paused while the app was closed (due to an event like a phone call or AirPods being taken out of ear etc)
My first approach was to run a func inside of viewWillAppear that checked
if mediaPlayer.playbackState == .paused {
...
}
If it was paused I updated the play/pause button image. However, this did not work, the play/pause button would still show Play even if it was paused.
Next, I tried adding an observer to the viewDidLoad
NotificationCenter.default.addObserver(self, selector: #selector(self.wasSongInterupted(_:)), name: UIApplication.didBecomeActiveNotification, object: self.mediaPlayer)
The self.wasSongInterupted I call is
#objc func wasSongInterupted(_ notification: Notification) {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .paused {
print("paused")
self.isPlaying = false
self.playPauseSongButton.isSelected = self.isPlaying
} else if self.mediaPlayer.playbackState == .playing {
self.isPlaying = true
self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
However, I am still having the same issue.
What is the best way to determine if my music player is playing or paused when I reopen the app?
Edit 1: I Edited my code based on comments.
wasSongInterrupted was not being called, and through breakpoints and errors I discovered the code was mostly not needed. I changed my code to be
func wasSongInterrupted() {
DispatchQueue.main.async {
if self.mediaPlayer.playbackState == .interrupted {
var isPlaying: Bool { return self.mediaPlayer.playbackState == .playing }
print("Playback state is \(self.mediaPlayer.playbackState.rawValue), self.isPlaying Bool is \(self.isPlaying)")
self.playPauseSongButton.setImage(UIImage(named: "playIconLight"), for: .normal)
//self.playPauseSongButton.isSelected = self.isPlaying
}
}
}
and inside my AppDelegate's applicationDidBecomeActive I have
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
Now the code runs, however I have an issue.
If I run the following code:
if self.mediaPlayer.playbackState == .interrupted {
print("interrupted \(self.isPlaying)")
}
and then make a call and come back to the app it will hit the breakpoint. It will print out interrupted as well as false which is the Bool value for self.isPlaying
However if I try to update the UI by
self.playPauseSongButton.isSelected = self.isPlaying
or by
self.playPauseSongButton.setImage(UIImage(named: "playIconLight.png"), for: .normal)
I get an error message Thread 1: EXC_BREAKPOINT (code=1, subcode=0x104af9258)
You trying to update you player UI from viewWillAppear. From Apple Documentation:
viewWillAppear(_:)
This method is called before the view controller's view is about to be added to a view hierarchy and before any animations are configured for showing the view.
So if your app was suspended and the becomes active again, this method won't be called, because your UIViewController is already at Navigations Stack.
If you want to catch the moment when your app becomes active from suspended state, you need to use AppDelegate. From Apple Documentation:
applicationDidBecomeActive(_:)
This method is called to let your app know that it moved from the inactive to active state. This can occur because your app was launched by the user or the system.
So you need to use this method at your AppDelegate to handle app running and update your interface.
UPDATE
You saying the inside this AppDelegate method you're doing
let mediaPlayerVC = MediaPlayerViewController()
mediaPlayerVC.wasSongInterupted()
That's wrong because you're creating a new view controller. What you need to do, is to access you existing view controller from navigation stack and update it.
One of the possible solutions is to use NotificationCenter to send a notification. You view controller should be subscribed to this event of course.
At first, you need to create a notification name
extension Notification.Name {
static let appBecameActive = Notification.Name(rawValue: "appBecameActive")
}
Then in you AppDelegate add following code to post your notifications when app becomes active
func applicationDidBecomeActive(_ application: UIApplication) {
NotificationCenter.default.post(name: .appBecameActive, object: nil)
}
And finally in your view controller add to subscribe it on notifications
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self,
selector: #selector(wakeUp),
name: .appBecameActive,
object: nil)
...
}
#objc func wakeUp() {
// Update your UI from here
}
Hope it helps you.

MBCalendarKit showing black screen in basic tabbed application

I'm fairly new to swift & Xcode and I'm using a basic "tabbed" application template in XCode and I am trying to implement the MBCalendarKit to show a calendar on the first tab view. I am using Cocoapods for the MBCalendar kit. The Basic tabbed application starts with the following files:
The author of MBCalendarKit explains that :
"You can show an instance of CKCalendarView. Use this if you want to manually manage your view hierarchy or have a finer control over your calendar view." :
/*
Here's how you'd show a CKCalendarView from within a view controller.
It's just four easy steps.
*/
// 0. In either case, import CalendarKit:
#import "CalendarKit/CalendarKit.h"
// 1. Instantiate a CKCalendarView
CKCalendarView *calendar = [CKCalendarView new];
// 2. Optionally, set up the datasource and delegates
[calendar setDelegate:self];
[calendar setDataSource:self];
// 3. Present the calendar
[[self view] addSubview:calendar];
And I also followed some of the example code that was provided although most of it was in objective-c. Currently when running the program, On the first tab view, I'm just getting a black screen. How can I fix this to present the calendar itself? I have also looked at the other CalendarKit posts on stackoverflow but none seem to tackle something as specific as this.
My FirstViewController.swift (Which is basically identical to that provided by the author) :
import UIKit
import MBCalendarKit
class FirstViewController: CKCalendarViewController, CKCalendarViewDataSource {
var data : NSMutableDictionary
required init(coder aDecoder: NSCoder) {
data = NSMutableDictionary()
super.init(coder: aDecoder)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
self.data = NSMutableDictionary()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Step 0 : Wire up the data source and delegate
self.delegate = self
self.dataSource = self
// Step 1 : Define some test events
let title : NSString = NSLocalizedString("Some random event", comment: "")
let date : NSDate = NSDate(day: 20, month: 12, year: 2015)
let event : CKCalendarEvent = CKCalendarEvent(title: title as String, andDate: date, andInfo: nil)
// Step 2 : Add the events to the cache array
self.data[date] = [event]
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//
// MARK: - CKCalendarDataSource
//
func calendarView(calendarView: CKCalendarView!, eventsForDate date: NSDate!) -> [AnyObject]! {
return self.data.objectForKey(date) as! [AnyObject]!
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
and the AppDelegate.swift:
import UIKit
import MBCalendarKit
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var viewController: CKCalendarViewController = FirstViewController(nibName: nil, bundle: nil)
self.window!.rootViewController = viewController
self.window!.makeKeyAndVisible()
return true
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}
Also when creating the FirstViewController() in the AppDelegate file I was a little unsure of the nibName and bundle arguments. If someone could please explain this as I tried to look it up on Apples reference documentation but it still wasn't clear, and for all I know the fact that I passe nil for both could be my issue? If anyone has experience with CalendarKit or can help me out that would be great,
Cheers

How call delegate function without present controller

I have Tap Bar Controller with 2 paths. One is settingController Other one is loginController and contactListController.
When i run the program the entry pint is set tocontactListControllerand if login is false apps shownloginController`. After login value is set on true and loginController is dismiss. On bottom i have Tab Bar Controller: ContactList | Settings
When i go to settings i have a LOGOUT button, i would like to do when i tap this how to set value login on false ? i have no segue between ContactList and SettingController
This is my ContactListController
class ContactsTableViewController: UITableViewController, SettingsControllerDelegate {
let settingsController: SettingsController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("settingsController") as! SettingsController
override func viewDidLoad() {
super.viewDidLoad()
settingsController.delegate = self
}
func didLogoutSuccessfully() {
loggedIn = false
}
}
This is settings controller
protocol SettingsControllerDelegate {
func didLogoutSuccessfully()
}
class SettingsController: UITableViewController {
var delegate: SettingsControllerDelegate?
fun tapButton() {
self.delegate?.didLogoutSuccessfully() // Set login as false
}
if i added
override func viewDidLoad() {
super.viewDidLoad(
settingsController.delegate = self
presentViewController(settingsController, animated: true, completion: nil)
}
my controller setting is appear first. How can i change this value in other way?
UPDATE
in contact list i have
var loggedIn: Bool = false {
didSet {
if loggedIn == true {
self.configureView()
}
}
}
override func viewDidAppear(animated: Bool) {
if loggedIn == false {
performSegueWithIdentifier("showLogin", sender: nil)
}
//tableView.reloadData()
}
Consider either 1: using notifications (to which all interested controllers are registered as observers) to react to session state changes, 2: moving your session state to something "higher up the chain" (like in or "hanging off of" your app delegate), or 3: making a singleton session controller.
1 can be used with 2 and 3 and either 2 or 3 make accessing the current state from anywhere in your app easier. I'd go with a mix of 1 and 3 myself.
This approach in general relieves you from having to walk and inspect the controller hierarchy to find and set the same thig on all other controllers (which is icky because it's so tightly coupled; changing the hierarchy and/or reusing VCs elsewhere would probably break things).
You can pass data between the tabs using UITabViewControllers.viewControllers method which returns array of the view controllers in the tab
//in SettingsVC
func viewWillDisapear(){
//assuming its in the second index of tabBar
let contactVC = self.tabBarController.viewControllers[1] as ContactsTableViewController
contactVC.delegate = self
contactVC.loggedIn = true //or false as you wish
super.viewWillDisapear()
}