Hard work to deal with state restoration in iOS - ios

How to deal with "Warning: Unable to create restoration in progress marker file."
I have assigned the Restoration Ids to my view controllers in main story board.
I have also assigned Restoration Id to the view controllers created by code.
For example:
#interface BEMenuController ()<UIViewControllerRestoration>
.....
+ (UIViewController *) viewControllerWithRestorationIdentifierPath: (NSArray *)identifierComponents coder:(NSCoder *)coder
{
UIViewController *retViewController = [[BEMenuController alloc] init];
return retViewController;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.restorationIdentifier = #"BEMenuController";
self.restorationClass = [self class];
..........
}
I did as in following link asked
state restoration in iOS
Now the problem is when I run the application I am getting the following warning also:
Unable to create restoration in progress marker file.
Not sure what else need to be done.

Related

Using applicationShouldTerminateAfterLastWindowClosed with Mac Catalyst

I have a Mac Catalyst app that's essentially a one-window app, but I added multi-window support (using scenes) to allow opening a second window for one function, which a small portion of users will use. Now Apple has rejected the app because with multi-window support, the app doesn't quit when a user clicks the red button at the top of the main window. One solution is to provide a menu item to reopen it, but I think it would be more intuitive for users if the app simply quit as it did before.
I found a similar problem on the Apple forums and am trying to implement the provided solution. Using this tutorial that provides more setup instructions, I have added a macOS bundle as a new target, embedded that into the iOS target, and added this class to the bundle:
#import "AppKitBridge.h"
#implementation AppKitBridge
#synthesize application;
#synthesize window;
- (id)init {
NSLog(#"AppKitBridge init");
self = [super init];
self.application = [NSApplication sharedApplication];
self.window = [[self.application windows] firstObject];
if (self.window) {
self.application.delegate = self;
self.window.delegate = self;
} else {
NSLog(#"AppKitBridge error: window is nil");
}
return self;
}
- (void)test {
NSArray *windows = NSApplication.sharedApplication.windows;
for (NSWindow *window in windows) {
NSLog(#"AppKitBridge window: %#", window);
}
}
- (void)applicationDidUpdate:(NSNotification *)notification {
NSLog(#"AppKitBridge applicationDidUpdate");
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
NSLog(#"AppKitBridge applicationShouldTerminateAfterLastWindowClosed");
return TRUE;
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSLog(#"AppKitBridge applicationShouldTerminate");
return TRUE;
}
#end
Then in viewDidLoad of the initial view controller of the iOS app, I call this method to load the bundle:
- (void)enableAppKit {
NSString *pluginPath = [[[NSBundle mainBundle] builtInPlugInsPath] stringByAppendingPathComponent:#"AppKit.bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:pluginPath];
[bundle load];
NSObject *appKit = [[[bundle classNamed:#"AppKitBridge"] alloc] init];
[appKit performSelector:#selector(test) withObject:nil afterDelay:0];
}
When I run the app, the console shows the AppKitBridge init, AppKitBridge window and AppKitBridge applicationDidUpdate lines. So it seems like the overall setup is working. But when I click the red window button, it does not show the AppKitBridge applicationShouldTerminateAfterLastWindowClosed or AppKitBridge applicationShouldTerminate lines, and the app does not quit.
Should this do what I'm expecting, and if so, what am I missing in the setup?
The problem is this line:
NSObject *appKit = [[[bundle classNamed:#"AppKitBridge"] alloc] init];
Your appKit object is a local variable so your AppKitBridge instance goes out of existence one line later. You need this object to persist if it is to function as the app/window delegate. Assign it to an instance property of some persistent object.

how to save and restore state Using storyboard in iOS?

I am working on chat application. I want to store the application state of all view controller.
My code to store app state:
+ (UIViewController *)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents coder:(NSCoder *)coder
{
MoreController *vc = nil;
UIStoryboard *storyboard = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (storyboard)
{
vc = (MoreController *)[storyboard instantiateViewControllerWithIdentifier:#"MoreController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MoreController class];
}
return vc;
}
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.view forKey:#"save"];
[coder encodeObject:_btnoiwii forKey:#"save"];
[coder encodeObject:_tblMore forKey:kUnsavedEditStateKey];
[super encodeRestorableStateWithCoder:coder];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder
{
self.view = [coder decodeObjectForKey:#"save"];
_btnoiwii= [coder decodeObjectForKey:#"save"];
_tblMore = [coder decodeObjectForKey:kUnsavedEditStateKey];
[super decodeRestorableStateWithCoder:coder];
}
I am able to encode the state of application but not decode. In app delegate class I added shouldRestoreApplicationState,willEncodeRestorableStateWithCoder
Please give me appropriate solution to save and restore state of application in iOS.
You should not save the whole table view and view controller's super view like that. When UIKit restores your view controller, it automatically restores all the views for you. Make sure you save your data source and when decoding, reload your table view.
If you want to restore indexes of your tableView, you need to conform to this protocol, UIDataSourceModelAssociation and implement these methods: indexPathForElementWithModelIdentifier, modelIdentifierForElementAtIndexPath
Also make sure that you are recovering the navigation stack where your view controllers are pushed.

CvPhotoCamera and memory warning

I have a question about CvPhotoCamera (OpenCV) in the iOS App.
I have a myViewController1: in this viewController I push a mySecondView controller.
In this second View Controller I use CvPhotoCamera:
I have a UIImageViewController.
In viewDidLoad I have this code:
- (void)viewDidLoad
{
[super viewDidLoad];
_photoCamera = [[CvPhotoCamera alloc] initWithParentView:imageView];
_photoCamera.defaultAVCaptureSessionPreset = AVCaptureSessionPresetPhoto;
_photoCamera.defaultAVCaptureVideoOrientation = AVCaptureVideoOrientationPortrait;
[_photoCamera setDefaultAVCaptureDevicePosition:AVCaptureDevicePositionBack];
_photoCamera.delegate = self;
[_photoCamera start];
}
in viewDidDisappear I have this code:
- (void) viewDidDisappear:(BOOL)animated
{
[_photoCamera stop];
}
I use CvPhotoCamera to take a photo from camera using the method:
- (IBAction)actionStart:(id)sender;
{
[_photoCamera takePicture];
}
The my problem is this:
When I push this second ViewController and I tap on back button on Nav bar I have some memoryWarning and the app crashes ..always!!!
I used also the profile Xcode tool to manage memory allocation or memory leak but I do not see anything strange.
Is correct this use of CvPhotoCamera obj?

Programmatically switch view in iOS

I'm new to iOS development and I'm trying to make a simple arithmetic game. It has two views, a kind of start screen with a button to start playing the game and a label with the high score on it, and the view to play the actual game. Here is a screenshot of it:
The code for the game's ViewController looks like this:
#import "AdditionController.h"
#import "ViewController.h"
#interface AdditionController ()
//properties and outlets here
#end
#implementation AdditionController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self setBackground];
self.difficulty = 20;
[self setupGame];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)setBackground
{
...
}
-(void)setupGame
{
...
}
-(void)writeQuestion
{
...
}
- (void)advanceTimer:(NSTimer *)timer
{
...
}
- (IBAction)checkAnswer:(id)sender {
...
}
- (IBAction)quit:(id)sender {
[self gameOver];
}
- (void)gameOver{
NSString *goodbye = [NSString stringWithFormat:#"You scored %d.", self.score_val];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
// to store
NSNumber *aNumber = [defaults objectForKey:#"hiScore"];
NSInteger anInt = [aNumber intValue];
if (anInt < self.score_val) {
[defaults setObject:[NSNumber numberWithInt:self.score_val] forKey:#"hiScore"];
[defaults synchronize];
goodbye = [goodbye stringByAppendingString:#"\nThat's a new high score!"];
}
UIAlertView *message = [[UIAlertView alloc] initWithTitle:#"Game Over!" message:goodbye delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[message show];
// I have been using this method to go back to the start screen.
// But this method doesn't update the new highscore
[self.navigationController popViewControllerAnimated:YES];
// However I should be able to do this if I can setup an ID for the start screen
ViewController *svc = [storyboard instantiateViewControllerWithIdentifier:#"StartViewController"];
}
#end
However when I go to View -> Utilities -> Show Identity Inspector, there is no option to set the story board ID, only one to set the restoration ID.
I was wondering if there was anyway to set the storyboard ID, or if I was missing something obvious? I'm using Xcode Version 5.0.2 (5A3005).
Also just wanted to add that I don't seem to be able to select UIViewController from the drop down list!
First, your screenshot clearly shows that you have the UIView selected in the storyboard, not the UIViewController, which is what you can set a storyboard ID on. So if you want to set an ID, you have to make sure you have the actual UIViewController selected.
Second, what you're trying to do by setting a storyboard ID is to create another instance of your first view controller, which is not what you want to do. You still have an instance of this view controller, you just want to get that instance and update the high score on it. You can do this in multiple ways:
You could create a delegate protocol on your second view controller, where it has a delegate method for updating the high score. Then you would set your first view controller as the delegate of the second, implement the delegate protocol method(s) in the first view controller, and call these at the appropriate time from the second view controller.
You could pass a block to the second view controller from the first that can be called with a high score (and any other important information) to let the first view controller know what to update.
You could use the UINavigationController to get the first view controller. If it is the root view controller of the navigation controller, then it'll be easy to get. Then you'll have your reference to the already existing instance and can update the high score however you were already planning on doing so. This one is the least ideal choice, however, because it requires that the second view controller have knowledge of the type of the first view controller, when really that shouldn't be necessary.
More just to add to Gavin's answer,
Whenever I've built simple games within an iOS app before i've created separate UIViews from the object library in storyboard, then I've put them outside the view controller's main view (i.e. move them below 'First Responder' in the document outline view). Then you can make these views properties of your viewController, and swap them in/out whenever you want with something like this when the start button is pressed to reveal self.gameView underneath:
[self.startView removeFromSuperview];
and the same would apply to reveal a self.finishView

Create a log in screen on iOS that will always show on top of all views

EDIT: Check below for solution.
I am working on a login screen for my app and I have it working for the most part except for a few edge cases. Ive set things up so that I have a segue from my UITabBar in the story board that I trigger in the app delegate applicationDidBecomeActive: method. As I said it works fine on all but one edge case I've found so far.
My app uses some modal view controllers, some of which are UIActivityViewControllers if that makes a difference, to enter and edit some core data entities. If one of these modal view controllers is opened when the app goes to the background, it will always show up when the app is reopened and my login doesn't show. I get the following console msg
Warning: Attempt to present <UINavigationController: 0x1d51e320> on <MPTabBarViewController: 0x1d5b4810> which is already presenting <UIActivityViewController: 0x1e38fc40>
Here is my code
- (void) displayLogin{
NSLog(#"%s", __PRETTY_FUNCTION__);
UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;
NSDate *lastDate = [[NSUserDefaults standardUserDefaults] objectForKey:MPLastCloseDate];
NSTimeInterval timeDiff = [[NSDate date] timeIntervalSinceDate:lastDate];
int seconds = timeDiff;
if ([[NSUserDefaults standardUserDefaults] integerForKey:MPPassCodeDelay] == MPScreenLockAlways || seconds >= 300) {
NSLog(#"Should see login");
[tabBarController performSegueWithIdentifier:#"loginScreen" sender:self];
}
}
I understand exactly what this msg is telling me, the tab bar is already presenting a modal controller so it can't present another one. So my question is this, Is there a better way to implement this so that the login will always show, even over top of the modal views?
Okay here is my current solution
as suggested by Bartu and requested to be shared by Shawn
I have a working singleton loginManager class that requires 1 call in app delegate and 1 call in any view controller that could be called to present as modal. I was unable to figure out how to do this as suggested with a ViewController category, but hey a few includes and method calls aren't so bad. I included it in App-Prefix.pch, so its available everywhere. It is written for ARC, so if you like managing your own memory you'll need to modify the singleton for that. The last caveat, at current you will need to roll your own viewController for the login screen. Just look for the commented section in the implementation with all the stars, and put your own view controller there. Mine is still in my app storyboard, its basically 4 digit pin that checks for a match in the keychain and dismisses itself for the correct pin. I may pull that out of my storyboard and nib it so it could be packaged with the loginManager and let it become my first gitHub project at some future date though.
You can configure it to display login for every time the app opens or after a delay with properties. The delay time is also a property set in seconds. It will also block out your apps UI for the few seconds it takes to get the login displayed with a splash using your apps Default.png. This is also configurable with a property.
I would love to get some feedback on this, and if anyone can tell me how to do a category so the extra call in viewControllers is not needed that would be great! Enjoy!
AppDelegate:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// 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.
[self.window makeKeyAndVisible];
// these calls are all optional
[[VHLoginManager loginManager] setShouldBlockUIWithSplashOnResume:NO];
[[VHLoginManager loginManager] setSecondsRequiredToPassBeforeLockDown:1000];
[[VHLoginManager loginManager] setScreenLockRequirment:VHLMScreenLockDelayed];
// this is the only required call to run with defaults - always login and block UI with splash while login loads
[[VHLoginManager loginManager] presentLogin];
}
Any viewController that may presented as modal at some point
- (void)viewDidLoad
{
[super viewDidLoad];
[[VHLoginManager loginManager] registerViewControllerIfModal:self];
}
The loginManager class
header:
// VHLoginManager.h
// Created by Victor Hudson on 5/31/13.
// Copyright (c) 2013 Victor Hudson. All rights reserved.
// Use if you like but be nice and leave my name
#import <Foundation/Foundation.h>
#define VHLMLastCloseDate #"VHLMLastCloseDate"
#define VHLMPassCodeDelay #"VHLMPassCodeDelay"
typedef enum {
VHLMScreenLockAlways = 0,
VHLMScreenLockDelayed = 1,
} VHLMScreenLockRequirement;
#interface VHLoginManager : NSObject
#property (nonatomic) BOOL shouldBlockUIWithSplashOnResume;
// defaults to yes so app contents arent visible before the login screen appears
#property (nonatomic) int secondsRequiredToPassBeforeLockDown;
// defaults to 5 minutes (300)
#pragma mark - Class Methods
+ (VHLoginManager *)loginManager;
// returns the singleton login manager
#pragma mark - Manager Methods
- (void) presentLogin;
// will determine if login should be presented an do so if needed
- (void) registerViewControllerIfModal:(UIViewController *)controller;
// any view controllers that are presented modally should call this with self as controller in viewDidLoad - the pupose of this manager is so login shows even over top of modals
- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement;
// deafaults to always if not adjusted
#end
implementation:
// VHLoginManager.m
// Created by Victor Hudson on 5/31/13.
// Copyright (c) 2013 Victor Hudson. All rights reserved.
// Use if you like but be nice and leave my name
#import "VHLoginManager.h"
static VHLoginManager *loginManager = nil;
#interface VHLoginManager ()
#property (nonatomic, strong) UIViewController *currentModalViewController;
#property (nonatomic) VHLMScreenLockRequirement screenLockrequirement;
#end
#implementation VHLoginManager
#pragma mark - Manager Methods
- (void) presentLogin
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if ([[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay] == VHLMScreenLockAlways || [self timeSinceLastClose] >= self.secondsRequiredToPassBeforeLockDown) {
//NSLog(#"User should see login");
// determine who the presenting view controller should be
UIViewController *viewController;
if (self.currentModalViewController && self.currentModalViewController.presentingViewController != nil) {
// NSLog(#"We have a modal view controller on top");
viewController = self.currentModalViewController;
} else {
// NSLog(#"We have NO modal view controller on top");
// get the root view controller of the app
viewController = [[[UIApplication sharedApplication] keyWindow] rootViewController];
}
//********************************************************************************************************************************************************************************
// *** This is still tied into my app storyboard and should be made into a viewcontroller with nib to be portable with loginManager for now implement and present your own loginViewController
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard_iPhone" bundle:nil];
UINavigationController *navController = [storyboard instantiateViewControllerWithIdentifier:#"appLoginScreen"];
//********************************************************************************************************************************************************************************
// present the login to user
[viewController presentViewController:navController animated:NO completion:nil];
}
}
- (void) setScreenLockRequirment:(VHLMScreenLockRequirement) requirement
{
_screenLockrequirement = requirement;
[[NSUserDefaults standardUserDefaults] setInteger:self.screenLockrequirement forKey:VHLMPassCodeDelay];
}
- (void) registerViewControllerIfModal:(UIViewController *)controller
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (controller.presentingViewController) {
NSLog(#"Registering a modalViewController");
self.currentModalViewController = controller;
}
}
#pragma mark - Private Methods
- (void) timeStampForBackground
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
[[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:VHLMLastCloseDate];
[self setDisplaySplashForBackgroundResume];
}
- (int) timeSinceLastClose
{
return [[NSDate date] timeIntervalSinceDate:[[NSUserDefaults standardUserDefaults] objectForKey:VHLMLastCloseDate]];
}
#pragma mark Splash Screen management
- (void) setDisplaySplashForBackgroundResume
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.shouldBlockUIWithSplashOnResume) {
// dismiss all keyboards and input views
UIView *topView = [[[[UIApplication sharedApplication] keyWindow] subviews] lastObject];
[topView endEditing:YES];
// Don't show a splash screen if the application is in UIApplicationStateInactive (lock/power button press)
UIApplication *application = [UIApplication sharedApplication];
if (application.applicationState == UIApplicationStateBackground) {
UIImageView *splash = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default"]];
splash.frame = application.keyWindow.bounds;
[application.keyWindow addSubview:splash];
}
}
}
- (void) removeSplashScreen
{
// NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.shouldBlockUIWithSplashOnResume) { // we should have a splash image up if true
// so remove it
UIWindow *thewindow = [[UIApplication sharedApplication] keyWindow];
if ([[thewindow subviews] count] > 1) {
[NSThread sleepForTimeInterval:1.0];
[[[thewindow subviews] lastObject] removeFromSuperview];
}
}
}
#pragma mark - Class Management
//prevent additional instances
+ (id)allocWithZone:(NSZone *)zone
{
return [self loginManager];
}
+ (VHLoginManager *)loginManager
{
if (!loginManager) {
//Create The singleton
loginManager = [[super allocWithZone:NULL] init];
}
return loginManager;
}
- (id) init
{
// If we already have an instance of loginManager
if (loginManager) {
//Return The Old One
return loginManager;
}
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(timeStampForBackground)
name:UIApplicationDidEnterBackgroundNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(removeSplashScreen)
name:UIApplicationDidBecomeActiveNotification
object:nil];
self.shouldBlockUIWithSplashOnResume = YES;
self.secondsRequiredToPassBeforeLockDown = 300;
if (![[NSUserDefaults standardUserDefaults] integerForKey:VHLMPassCodeDelay]) {
[self setScreenLockRequirment:VHLMScreenLockAlways];
}
}
return self;
}
#end
I had the same problem a short time ago, my solution for this problem is to have a reference to any modal view which is currently presented in your app delegate. So, you can know if your tab bar controller is already presenting a modal controller and if it's the case, you can present your login view over your current modal view.
What I did was to have a switch in my appDelegate. when the app started, if the user had not logged in, I created the login view and make it the window's rootViewController. When the user successfully logged in, I used an animation block to set that view's alpha to 0, then created a UITabBarController, populated it, made it the window's rootViewController (with an alpha of 0, then animated it's alpha to 1). Worked really well. Not sure how to do this with storyboards though.
EDIT: now getting familiar with storyboards. So what you would do is not use the Main.storyboard per se (remove it from info.plist), then add a LoginViewController as a view, and have your UITabbarController there too - but nothing is the initial view controller. You obviously have to name each view so you can create it in code, but asking the Storyboard to create such and such a view controller
So in App Delegate, if logged in, instantiate the tab bar controller and add it as the root view controller. If the user has not logged in, create the LoginView and add it as rootview controller. If the user does login, have some method on the LoginViewController so it can ask the delegate to switch to the tab bar controller.

Resources