SIMBL swizzling in finder - objective-c

I manages to integrate Icon Overlay like dropbox in Mac Os Finder with a SIMBL plugin !
I Use the swizzle method to override some finder function.
Here is my swizzle method :
void PluginSwizzleInstanceMethod(Class cls, SEL oldSel, SEL newSel)
{
Method mnew = class_getInstanceMethod(cls, newSel);
Method mold = class_getInstanceMethod(cls, oldSel);
method_exchangeImplementations(mnew, mold);
}
So for the moment i override the drawWithFrame method like this :
Class cls = NSClassFromString(#"TIconAndTextCell");
SEL oldSel = #selector(drawWithFrame:inView:);
SEL newSel = #selector(PluginDrawWithFrame:inView:);
PluginSwizzleInstanceMethod(cls, oldSel, newSel);
all my icons are displayed but not on my desktop or on the view that show items as icon ...
Why ?
thank you ,

I just managed to do some stuff for the Desktop, so I'll post in case that's still relevant for someone:
So as you experienced, Finder draws icons differently on the Desktop.
One class that is responsible for that is TDesktopIcon.
One can, for example, change icons by overriding the drawIconInContext: method, and setting the thumbnailImage property to whatever NSImage* he likes.
- (void) FO_drawIconInContext:(struct CGContext *)arg1 {
NSImage *image;
// set image...
[(TDesktopIcon*)self setThumbnailImage:image];
[self FO_drawIconInContext:arg1];
}
Getting the url for each item was not as straight forward as with TIconAndTextCell or IKImageBrowserCell.
In order to get the URLs, you need to override the prepareToDrawNode: method of TDesktopViewController.
You get the URL from arg1 as follows:
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1 {
struct OpaqueNodeRef *opr = arg1->fNodeRef;
struct FINode *fiNode = [FINode nodeFromNodeRef:opr];
NSURL *url = [fiNode previewItemURL];
// save url somewhere (I use NSUserDefaults)
[self FO_prepareToDrawNode:arg1];
}
Maybe there's a better way, but that's what I figured out...

Got it! (THIS CODE ONLY WORKS FOR 10.7 THOUGH) I was having issues because the icon was being drawn before the final image was rendered to the thumbnail image.... so I got these artifacts where the original icon was missing....but the overlay was always there...it was the order of draw operations I had wrong...!
I even have the name for the target node... you need the TFENodeHelper class to translate the TFENode into a filePath...
//
// DesktopViewIconOverlay.h
// FinderIconOverlayExample
//
// Created by Orbitus007 on 2/20/13.
//
//
#import <Foundation/Foundation.h>
#import <Quartz/Quartz.h>
#import "Finder.h"
#interface DesktopViewIconOverlay : NSObject
+ (void)pluginLoad;
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1;
- (void) FO_drawIconInContext:(struct CGContext *)arg1;
#end
//
// DesktopViewIconOverlay.m
// FinderIconOverlayExample
//
// Created by Orbitus007 on 2/20/13.
//
//
#import "DesktopViewIconOverlay.h"
#import "FinderIconOverlayExample.h"
#include <objc/objc.h>
#include <objc/runtime.h>
#import <Quartz/Quartz.h>
#import "TFENodeHelper.h"
static TFENodeHelper *gNodeHelper;
#implementation DesktopViewIconOverlay
+ (void)pluginLoad
{
// Create helper object
gNodeHelper = [[TFENodeHelper alloc] init];
if (gNodeHelper == nil) {
NSLog(#"Failed to instantiate 'TFENodeHelper' class");
return;
}
Method old, new;
Class self_class = [self class];
Class finder_class = [objc_getClass("TDesktopIcon") class];
class_addMethod(finder_class, #selector(FO_drawIconInContext:),
class_getMethodImplementation(self_class, #selector(FO_drawIconInContext:)),"v#:#");
old = class_getInstanceMethod(finder_class, #selector(drawIconInContext:));
new = class_getInstanceMethod(finder_class, #selector(FO_drawIconInContext:));
method_exchangeImplementations(old, new);
finder_class = [objc_getClass("TDesktopViewController") class];
class_addMethod(finder_class, #selector(FO_prepareToDrawNode:),
class_getMethodImplementation(self_class, #selector(FO_prepareToDrawNode:)),"v#:#");
old = class_getInstanceMethod(finder_class, #selector(prepareToDrawNode:));
new = class_getInstanceMethod(finder_class, #selector(FO_prepareToDrawNode:));
method_exchangeImplementations(old, new);
}
- (void) FO_prepareToDrawNode:(const struct TFENode *)arg1 {
NSString *path = [gNodeHelper pathForNode:arg1];
NSLog(#"Path = %#", path);
[[NSUserDefaults standardUserDefaults] setValue:path forKey:#"TDesktopIconURL"];
//struct OpaqueNodeRef *opr = arg1->fNodeRef;
//NSLog(#"path = %#", [[FINode nodeWithFENode:arg1] fullPath]);
//id fiNode = [FINode nodeFromNodeRef:opr];
//NSURL *url = [fiNode previewItemURL];
//[[NSUserDefaults standardUserDefaults] setValue:[url path] forKey:#"TDesktopIconURL"];
//NSLog(#"sending ", arg1->fNodeRef);
// save url somewhere (I use NSUserDefaults)
[self FO_prepareToDrawNode:arg1];
}
- (void) FO_drawIconInContext:(struct CGContext *)arg1
{
[self FO_drawIconInContext:arg1];
NSString *iconPath = [[NSUserDefaults standardUserDefaults] valueForKey:#"TDesktopIconURL"];
NSLog(#"recieved %#", iconPath);
if (![iconPath isEqualToString:#"/Users/h0xff/Desktop/Restart Finder.app"])
return;
NSString *imagePath = #"/Users/h0xff/Desktop/020513_icons/128_synced_green.png";
NSImage *overlay = [[NSImage alloc] initWithContentsOfFile:imagePath];
NSImage *mainImage = [(TDesktopIcon*)self thumbnailImage];
float width = 256.0;
float height = 256.0;
NSImage *finalImage = [[NSImage alloc] initWithSize:NSMakeSize(width, height)];
[finalImage lockFocus];
// draw the base image
[mainImage drawInRect:NSMakeRect(0, 0, width, height)
fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
// draw the overlay image at some offset point
[overlay drawInRect:NSMakeRect(0, 0, [overlay size].width/1.5, [overlay size].height/1.5)
fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1.0];
[finalImage unlockFocus];
// set image...
[(TDesktopIcon*)self setThumbnailImage:finalImage];
[self FO_drawIconInContext:arg1];
}
#end
This code was derived from Alexey Zhuchkov and many others.... please ask questions if you have any!
//
// TFENodeHelper.h
// FinderMenu
//
// Helper class to get full path from TFENode
//
// Created by Alexey Zhuchkov on 11/4/12.
// Copyright (c) 2012 InfiniteLabs. All rights reserved.
//
#import <Foundation/Foundation.h>
// Forward declarations
struct TFENode;
typedef enum {
TFENodeCtorTypeUnknown,
TFENodeCtorTypeNodeWithFENode, // nodeWithFENode
TFENodeCtorTypeNodeFromNodeRef, // nodeFromNodeRef
} TFENodeCtorType;
typedef enum {
TFENodePathMethodTypeUnknown,
TFENodePathMethodTypeFullPath, // fullPath
TFENodePathMethodTypePreviewItemURL, // previewItemURL
} TFENodePathMethodType;
#interface TFENodeHelper : NSObject {
#private
Class _class;
TFENodeCtorType _ctorType;
TFENodePathMethodType _pathMethodType;
}
- (NSString *)pathForNode:(const struct TFENode *)node;
#end
//
// TFENodeHelper.m
// FinderMenu
//
// Created by Alexey Zhuchkov on 11/4/12.
// Copyright (c) 2012 InfiniteLabs. All rights reserved.
//
#import "TFENodeHelper.h"
#import <objc/runtime.h>
#import "Finder.h"
#implementation TFENodeHelper
- (id)init {
self = [super init];
if (self) {
_class = NSClassFromString(#"FINode");
_ctorType = TFENodeCtorTypeUnknown;
_pathMethodType = TFENodePathMethodTypeUnknown;
if (_class) {
if (class_getClassMethod(_class, #selector(nodeWithFENode:))) {
_ctorType = TFENodeCtorTypeNodeWithFENode;
} else if (class_getClassMethod(_class, #selector(nodeFromNodeRef:))) {
_ctorType = TFENodeCtorTypeNodeFromNodeRef;
}
if (class_getInstanceMethod(_class, #selector(fullPath))) {
_pathMethodType = TFENodePathMethodTypeFullPath;
} else if (class_getInstanceMethod(_class, #selector(previewItemURL))) {
_pathMethodType = TFENodePathMethodTypePreviewItemURL;
}
}
if (!_class
|| (_ctorType == TFENodePathMethodTypeUnknown)
|| (_pathMethodType == TFENodePathMethodTypeUnknown)) {
[self release];
return nil;
}
}
return self;
}
- (NSString *)pathForNode:(const struct TFENode *)node {
FINode *fiNode = nil;
NSString *path = nil;
switch (_ctorType) {
case TFENodeCtorTypeNodeWithFENode:
fiNode = [_class nodeWithFENode:node];
break;
case TFENodeCtorTypeNodeFromNodeRef:
fiNode = [_class nodeFromNodeRef:node->fNodeRef];
break;
default:
break;
}
NSURL *url;
if (fiNode) {
switch (_pathMethodType) {
case TFENodePathMethodTypeFullPath:
path = [fiNode fullPath];
break;
case TFENodePathMethodTypePreviewItemURL:
url = [fiNode previewItemURL];
path = [url path];
default:
break;
}
}
return path;
}
#end

Related

iOS(Objective-C) Crashed for EXC_BAD_ACCESS,when get and set a atomic Global variable in Multithreading

My app crashed, suspected to be caused by multi-threaded operation of an attribute in a singleton object.
So I wrote a small piece of code and successfully reproduced the problem, but I still couldn't understand it.
I have defined the property as #property, which is atomic. Why does it still crash when accessed by multiple threads? Below is my code snippet:
Audio.h
#interface Audio : NSObject
#property NSString *audioName;
#property NSString *audioData;
#end
Audio.m
#import "Audio.h"
#implementation Audio
- (instancetype)init{
self = [super init];
if (self) {
_audioData = #"";
_audioName = nil;
}
return self;
}
#end
AudioManager.h
#interface AudioManager : NSObject
+(instancetype)shareInstance;
#property Audio *curAudio;
-(void) play;
-(void) clearCurAudio;
#end
AudioManager.m
#import "AudioManager.h"
#implementation AudioManager
static id sharedInstance = nil;
+(instancetype)shareInstance {
static dispatch_once_t predicate;
dispatch_once(&predicate, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
-(void) play {
NSLog(#"Current Audio name : %#",_curAudio.audioName);
NSLog(#"Current Audio name : %#",_curAudio.audioData);
NSLog(#"Current Audio name : %#",_curAudio.audioName);//crahed here!
NSLog(#"Current Audio name : %#",_curAudio.audioData);
}
-(void) clearCurAudio {
_curAudio = nil;
}
#end
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_queue_t thread1 = dispatch_queue_create("queue1", nil);
dispatch_queue_t thread2 = dispatch_queue_create("queue2", nil);
dispatch_queue_t thread3 = dispatch_queue_create("queue3", nil);
dispatch_async(thread1, ^{
for (int i = 0; i < 1000; i++) {
Audio *newAudio = [[Audio alloc] init];
newAudio.audioName = #"na";
[[AudioManager shareInstance] setCurAudio:newAudio];
}
});
//
dispatch_async(thread2, ^{
for (int i = 0; i < 1000; i++) {
[[AudioManager shareInstance] play];
}
});
//
dispatch_async(thread3, ^{
for (int i = 0; i < 1000; i++) {
AudioManager * audioManager = [AudioManager shareInstance];
[[AudioManager shareInstance] clearCurAudio];
}
});
}
Here is the crash EXC_BAD_ACCESS:
enter image description here
Thank you guys,problem solved!!, as #Willeke posted.
use self.curAudio rather than _curAudio.

Object loses reference

I have a IKImageBrowserView which has its own controller - BrowserController.h + .m
#interface BrowserController : NSWindowController{
NSMutableArray *_images;
}
#property (strong,nonatomic) IBOutlet IKImageBrowserView *imageBrowser;
-(void)addMultipleImages;
When I run the app for the first time, the staring image loads, but when I click a button to add other images and call a method from another class I don't get any results. I have noticed that my _imageBrowser loses the reference and becomes nil.
How could I solve this issue?
AppDelegate.m
#import "AppDelegate.h"
#import "BrowserController.h"
#implementation AppDelegate{
BrowserController *imageBrowserController;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Insert code here to initialize your application
imageBrowserController = [BrowserController sharedManager];
}
- (IBAction)doSomething:(id)sender {
[imageBrowserController addMultipleImages];
}
#end
BrowserController.m
#import "BrowserController.h"
#interface myImageObject : NSObject
{
NSString *_path;
}
#end
#implementation myImageObject
/* our datasource object is just a filepath representation */
- (void)setPath:(NSString *)path
{
if(_path != path)
{
_path = path;
}
}
/* required methods of the IKImageBrowserItem protocol */
#pragma mark -
#pragma mark item data source protocol
/* let the image browser knows we use a path representation */
- (NSString *)imageRepresentationType
{
return IKImageBrowserPathRepresentationType;
}
/* give our representation to the image browser */
- (id)imageRepresentation
{
return _path;
}
/* use the absolute filepath as identifier */
- (NSString *)imageUID
{
return _path;
}
#end
#interface BrowserController ()
#end
#implementation BrowserController
- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code here.
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect
{
[super drawRect:dirtyRect];
// Drawing code here.
}
- (void)awakeFromNib{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
- (void)updateDataSource{
[_imageBrowser reloadData];
}
-(NSUInteger) numberOfItemsInImageBrowser:(IKImageBrowserView *)aBrowser{
return [_images count];
};
-(id)imageBrowser:(IKImageBrowserView *)aBrowser itemAtIndex:(NSUInteger)index{
return [_images objectAtIndex:index];
};
- (void)updateDatasource
{
[_imageBrowser reloadData];
}
- (void)addMultipleImages{
myImageObject *p;
p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[_images addObject:p];
[_images addObject:p];
[_imageBrowser reloadData];
}
+ (id)sharedManager {
static BrowserController *sharedMyManager = nil;
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
#end
There is some confusion as to the name of the class where you mention it's called ImageBrowserController at the start of your question and it looks like it's called BrowserController at the end of your question.
The issue is that you allocate _images in awakeFromNib which is never called given the class is created via the singleton pattern (sharedInstance) and not loaded from a .nib file.
Therefore move the code from awakeFromNib into init and dump awakeFromNib:
BrowserController.m:
- (id)init
{
self = [super init];
if (self) {
myImageObject *p = [[myImageObject alloc]init];
[p setPath:[[NSBundle mainBundle]pathForResource:#"Unknown" ofType:#"jpg"]];
_images = [[NSMutableArray alloc]init];
[_images addObject:p];
[self updateDataSource];
}
return self;
}
Further confusion: you have implemented an initWithFrame method. Why?

SplitViewController with a Login View Controller as the root

So I've been researching on how to make a login view controller as the initial view controller instead of the splitview.
Some of the answers I've seen would recommend a modal view to be loaded? I'm not sure how that is set up.
eg.
How to add a login view before a UISplitViewController iPad
and
How to implement SplitViewController on second level.?
So do I add those on the loginviewcontroller class? Or where?
Any advice is welcome.
Thanks!!
I've done this by creating two storyboards: one with the (full-screen) login and one with the split-view.
To switch between them, I've added a custom protocol:
#import <Foundation/Foundation.h>
#protocol RootViewControllerDelegate <NSObject>
-(void)switchToStoryboard: (UIStoryboard *) storyboad animationDirectionOrNil: (NSString *)direction;
#end
The AppDelegate then implements this protocol:
-(void)switchToStoryboard:(id)storyboad animationDirectionOrNil:(NSString *)direction {
UIViewController *newRoot=[storyboad instantiateInitialViewController];
if ([newRoot respondsToSelector:#selector(setRootViewControllerDelegate:)]) {
[newRoot setRootViewControllerDelegate:self];
}
self.window.rootViewController=newRoot;
if(direction){
CATransition* transition=[CATransition animation];
transition.type=kCATransitionPush;
transition.subtype=direction;
[self.window.layer addAnimation:transition forKey:#"push_transition"];
}
}
As you can see, it tries to set itself as the delegate again, so the other view-controller can switch back or to another storyboard. In order for this to work, you would have to subclass UISplitView:
Header
#import <UIKit/UIKit.h>
#import "RootViewControllerDelegate.h"
#interface MySplitViewController : UISplitViewController
#property (nonatomic, weak) id <RootViewControllerDelegate> rootViewControllerDelegate;
#end
iMplementation
#import "MySplitViewController.h"
#implementation MySplitViewController
#synthesize rootViewControllerDelegate;
- (void)viewDidLoad
{
[super viewDidLoad];
for (UIViewController *viewController in self.viewControllers) {
if ([viewController respondsToSelector:#selector(setRootViewControllerDelegate:)]) {
[viewController setRootViewControllerDelegate:self.rootViewControllerDelegate];
}
}
}
#end
This simple implementation looks for child-view-controllers that accept a root-view-controller-delegate and hands it down. So when you want to add a "Show Login"-button to a certain (master- or detail-)view, just create your own UIViewController-subclass, add a #property id<RootViewControllerDelegate> rootViewControllerDelegate and associate an action like this with the button:
- (IBAction)loginButtonClicked:(id)sender {
UIStoryboard *mainSB=[UIStoryboard storyboardWithName:#"LoginStoryboard" bundle:nil];
NSString *animationDirection=kCATransitionFromTop;
UIDeviceOrientation currentOrientation=[[UIDevice currentDevice] orientation];
if (currentOrientation==UIDeviceOrientationLandscapeLeft) {
animationDirection=kCATransitionFromBottom;
}
[self.rootViewControllerDelegate switchToStoryboard:mainSB animationDirectionOrNil:animationDirection];
}
Feel free to adjust everything to your needs.
well here it is my friend. i created a ibaction in in btn and pushed the new view with modal option of the story board. them i plugged in the classes for the login view which also refers to constants that keeps the record strait. then after login is recognized i pushed a new view. bare in mind i was having the users create a password in their device and not importing it from the server. if you want to import it from the server it will be different.
here is the log in .h
#import <UIKit/UIKit.h>
#import "Constants.h"
#interface LogInViewController : UIViewController<UITextFieldDelegate>
#property (nonatomic) BOOL pinValidated;
#end
and here is the code for login .m
#import "LogInViewController.h"
#import "KeychainWrapper.h"
#interface LogInViewController ()
#end
#implementation LogInViewController
#synthesize pinValidated;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
// Helper method to congregate the Name and PIN fields for validation.
- (BOOL)credentialsValidated
{
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
BOOL pin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
if (name && pin) {
return YES;
} else {
return NO;
}
}
- (void)presentAlertViewForPassword
{
// 1
BOOL hasPin = [[NSUserDefaults standardUserDefaults] boolForKey:PIN_SAVED];
// 2
if (hasPin) {
// 3
NSString *user = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
NSString *message = [NSString stringWithFormat:#"What is %#'s password?", user];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Enter Password"
message:message
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Done", nil];
// 4
[alert setAlertViewStyle:UIAlertViewStyleSecureTextInput]; // Gives us the password field
alert.tag = kAlertTypePIN;
// 5
UITextField *pinField = [alert textFieldAtIndex:0];
pinField.delegate = self;
pinField.autocapitalizationType = UITextAutocapitalizationTypeWords;
pinField.tag = kTextFieldPIN;
[alert show];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Setup Credentials"
message:#"Enter Your information!"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Done", nil];
// 6
[alert setAlertViewStyle:UIAlertViewStyleLoginAndPasswordInput];
alert.tag = kAlertTypeSetup;
UITextField *nameField = [alert textFieldAtIndex:0];
nameField.autocapitalizationType = UITextAutocapitalizationTypeWords;
nameField.placeholder = #"Name"; // Replace the standard placeholder text with something more applicable
nameField.delegate = self;
nameField.tag = kTextFieldName;
UITextField *passwordField = [alert textFieldAtIndex:1]; // Capture the Password text field since there are 2 fields
passwordField.delegate = self;
passwordField.tag = kTextFieldPassword;
[alert show];
}
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (alertView.tag == kAlertTypePIN) {
if (buttonIndex == 1 && self.pinValidated) { // User selected "Done"
[self performSegueWithIdentifier:#"ScoutSegue" sender:self];
self.pinValidated = NO;
} else { // User selected "Cancel"
[self presentAlertViewForPassword];
}
} else if (alertView.tag == kAlertTypeSetup) {
if (buttonIndex == 1 && [self credentialsValidated]) { // User selected "Done"
[self performSegueWithIdentifier:#"ScoutSegue" sender:self];
} else { // User selected "Cancel"
[self presentAlertViewForPassword];
}
}
}
#pragma mark - Text Field + Alert View Methods
- (void)textFieldDidEndEditing:(UITextField *)textField
{
// 1
switch (textField.tag) {
case kTextFieldPIN: // We go here if this is the 2nd+ time used (we've already set a PIN at Setup).
NSLog(#"User entered PIN to validate");
if ([textField.text length] > 0) {
// 2
NSUInteger fieldHash = [textField.text hash]; // Get the hash of the entered PIN, minimize contact with the real password
// 3
if ([KeychainWrapper compareKeychainValueForMatchingPIN:fieldHash]) { // Compare them
NSLog(#"** User Authenticated!!");
self.pinValidated = YES;
} else {
NSLog(#"** Wrong Password :(");
self.pinValidated = NO;
}
}
break;
case kTextFieldName: // 1st part of the Setup flow.
NSLog(#"User entered name");
if ([textField.text length] > 0) {
[[NSUserDefaults standardUserDefaults] setValue:textField.text forKey:USERNAME];
[[NSUserDefaults standardUserDefaults] synchronize];
}
break;
case kTextFieldPassword: // 2nd half of the Setup flow.
NSLog(#"User entered PIN");
if ([textField.text length] > 0) {
NSUInteger fieldHash = [textField.text hash];
// 4
NSString *fieldString = [KeychainWrapper securedSHA256DigestHashForPIN:fieldHash];
NSLog(#"** Password Hash - %#", fieldString);
// Save PIN hash to the keychain (NEVER store the direct PIN)
if ([KeychainWrapper createKeychainValue:fieldString forIdentifier:PIN_SAVED]) {
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:PIN_SAVED];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"** Key saved successfully to Keychain!!");
}
}
break;
default:
break;
}
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.pinValidated = NO;
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self presentAlertViewForPassword];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
here is the code for the constant
// Used for saving to NSUserDefaults that a PIN has been set, and is the unique identifier for the Keychain.
#define PIN_SAVED #"hasSavedPIN"
// Used for saving the user's name to NSUserDefaults.
#define USERNAME #"username"
// Used to specify the application used in accessing the Keychain.
#define APP_NAME [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleIdentifier"]
// Used to help secure the PIN.
// Ideally, this is randomly generated, but to avoid the unnecessary complexity and overhead of storing the Salt separately, we will standardize on this key.
// !!KEEP IT A SECRET!!
#define SALT_HASH #"FvTivqTqZXsgLLx1v3P8TGRyVHaSOB1pvfm02wvGadj7RLHV8GrfxaZ84oGA8RsKdNRpxdAojXYg9iAj"
// Typedefs just to make it a little easier to read in code.
typedef enum {
kAlertTypePIN = 0,
kAlertTypeSetup
} AlertTypes;
typedef enum {
kTextFieldPIN = 1,
kTextFieldName,
kTextFieldPassword
} TextFieldTypes;
here is the keychainwrapper
#import <Foundation/Foundation.h>
#import <Security/Security.h>
#import <CommonCrypto/CommonHMAC.h>
#interface KeychainWrapper : NSObject
// Generic exposed method to search the keychain for a given value. Limit one result per search.
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier;
// Calls searchKeychainCopyMatchingIdentifier: and converts to a string value.
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier;
// Simple method to compare a passed in hash value with what is stored in the keychain.
// Optionally, we could adjust this method to take in the keychain key to look up the value.
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash;
// Default initializer to store a value in the keychain.
// Associated properties are handled for you - setting Data Protection Access, Company Identifer (to uniquely identify string, etc).
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
// Updates a value in the keychain. If you try to set the value with createKeychainValue: and it already exists,
// this method is called instead to update the value in place.
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier;
// Delete a value in the keychain.
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier;
// Generates an SHA256 (much more secure than MD5) hash.
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash;
+ (NSString*)computeSHA256DigestForString:(NSString*)input;
#end
and finally here is the code for the keychainwrapper .m
#import "KeychainWrapper.h"
#import "Constants.h"
#implementation KeychainWrapper
// *** NOTE *** This class is ARC compliant - any references to CF classes must be paired with a "__bridge" statement to
// cast between Objective-C and Core Foundation Classes. WWDC 2011 Video "Introduction to Automatic Reference Counting" explains this.
// *** END NOTE ***
+ (NSMutableDictionary *)setupSearchDirectoryForIdentifier:(NSString *)identifier {
// Setup dictionary to access keychain.
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
// Specify we are using a password (rather than a certificate, internet password, etc).
[searchDictionary setObject:( id)kSecClassGenericPassword forKey:( id)kSecClass];
// Uniquely identify this keychain accessor.
[searchDictionary setObject:APP_NAME forKey:( id)kSecAttrService];
// Uniquely identify the account who will be accessing the keychain.
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:( id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:( id)kSecAttrAccount];
return searchDictionary;
}
+ (NSData *)searchKeychainCopyMatchingIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
// Limit search results to one.
[searchDictionary setObject:( id)kSecMatchLimitOne forKey:( id)kSecMatchLimit];
// Specify we want NSData/CFData returned.
[searchDictionary setObject:( id)kCFBooleanTrue forKey:( id)kSecReturnData];
// Search.
NSData *result = nil;
CFTypeRef foundDict = NULL;
OSStatus status = SecItemCopyMatching(( CFDictionaryRef)searchDictionary, &foundDict);
if (status == noErr) {
result = ( NSData *)foundDict;
} else {
result = nil;
}
return result;
}
+ (NSString *)keychainStringFromMatchingIdentifier:(NSString *)identifier
{
NSData *valueData = [self searchKeychainCopyMatchingIdentifier:identifier];
if (valueData) {
NSString *value = [[NSString alloc] initWithData:valueData
encoding:NSUTF8StringEncoding];
return value;
} else {
return nil;
}
}
+ (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *dictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:( id)kSecValueData];
// Protect the keychain entry so it's only valid when the device is unlocked.
[dictionary setObject:( id)kSecAttrAccessibleWhenUnlocked forKey:( id)kSecAttrAccessible];
// Add.
OSStatus status = SecItemAdd(( CFDictionaryRef)dictionary, NULL);
// If the addition was successful, return. Otherwise, attempt to update existing key or quit (return NO).
if (status == errSecSuccess) {
return YES;
} else if (status == errSecDuplicateItem){
return [self updateKeychainValue:value forIdentifier:identifier];
} else {
return NO;
}
}
+ (BOOL)updateKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:valueData forKey:( id)kSecValueData];
// Update.
OSStatus status = SecItemUpdate(( CFDictionaryRef)searchDictionary,
( CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
} else {
return NO;
}
}
+ (void)deleteItemFromKeychainWithIdentifier:(NSString *)identifier
{
NSMutableDictionary *searchDictionary = [self setupSearchDirectoryForIdentifier:identifier];
CFDictionaryRef dictionary = ( CFDictionaryRef)searchDictionary;
//Delete.
SecItemDelete(dictionary);
}
+ (BOOL)compareKeychainValueForMatchingPIN:(NSUInteger)pinHash
{
if ([[self keychainStringFromMatchingIdentifier:PIN_SAVED] isEqualToString:[self securedSHA256DigestHashForPIN:pinHash]]) {
return YES;
} else {
return NO;
}
}
// This is where most of the magic happens (the rest of it happens in computeSHA256DigestForString: method below).
// Here we are passing in the hash of the PIN that the user entered so that we can avoid manually handling the PIN itself.
// Then we are extracting the username that the user supplied during setup, so that we can add another unique element to the hash.
// From there, we mash the user name, the passed-in PIN hash, and the secret key (from ChristmasConstants.h) together to create
// one long, unique string.
// Then we send that entire hash mashup into the SHA256 method below to create a "Digital Digest," which is considered
// a one-way encryption algorithm. "One-way" means that it can never be reverse-engineered, only brute-force attacked.
// The algorthim we are using is Hash = SHA256(Name + Salt + (Hash(PIN))). This is called "Digest Authentication."
+ (NSString *)securedSHA256DigestHashForPIN:(NSUInteger)pinHash
{
// 1
NSString *name = [[NSUserDefaults standardUserDefaults] stringForKey:USERNAME];
name = [name stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// 2
NSString *computedHashString = [NSString stringWithFormat:#"%#%i%#", name, pinHash, SALT_HASH];
// 3
NSString *finalHash = [self computeSHA256DigestForString:computedHashString];
NSLog(#"** Computed hash: %# for SHA256 Digest: %#", computedHashString, finalHash);
return finalHash;
}
// This is where the rest of the magic happens.
// Here we are taking in our string hash, placing that inside of a C Char Array, then parsing it through the SHA256 encryption method.
+ (NSString*)computeSHA256DigestForString:(NSString*)input
{
const char *cstr = [input cStringUsingEncoding:NSUTF8StringEncoding];
NSData *data = [NSData dataWithBytes:cstr length:input.length];
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
// This is an iOS5-specific method.
// It takes in the data, how much data, and then output format, which in this case is an int array.
CC_SHA256(data.bytes, data.length, digest);
// Setup our Objective-C output.
NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
// Parse through the CC_SHA256 results (stored inside of digest[]).
for(int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
[output appendFormat:#"%02x", digest[i]];
}
return output;
}
#end
this is the blueprint for creating a login view before any other view weather it be view controller or any other view such as tab bar or so. take a good look at this set of codes and modify them as you please. hope this helps you my friend. the codes are there all you have to do is study them and modify them to what you want. happy coding.

Custom property animation with actionForKey: how do I get the new value for the property?

In a CALayer subclass I'm working on I have a custom property that I want to animate automatically, that is, assuming the property is called "myProperty", I want the following code:
[myLayer setMyProperty:newValue];
To cause a smooth animation from the current value to "newValue".
Using the approach of overriding actionForKey: and needsDisplayForKey: (see following code) I was able to get it to run very nicely to simply interpolate between the old and and new value.
My problem is that I want to use a slightly different animation duration or path (or whatever) depending on both the current value and the new value of the property and I wasn't able to figure out how to get the new value from within actionForKey:
Thanks in advance
#interface ERAnimatablePropertyLayer : CALayer {
float myProperty;
}
#property (nonatomic, assign) float myProperty;
#end
#implementation ERAnimatablePropertyLayer
#dynamic myProperty;
- (void)drawInContext:(CGContextRef)ctx {
... some custom drawing code based on "myProperty"
}
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
theAnimation.fromValue = [[self presentationLayer] valueForKey:key];
... I want to do something special here, depending on both from and to values...
return theAnimation;
}
return [super actionForKey:key];
}
+ (BOOL)needsDisplayForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"])
return YES;
return [super needsDisplayForKey:key];
}
#end
You need to avoid custom getters and setters for properties you want to animate.
Override the didChangeValueForKey: method. Use it to set the model value for the property you want to animate.
Don't set the toValue on the action animation.
#interface MyLayer: CALayer
#property ( nonatomic ) NSUInteger state;
#end
-
#implementation MyLayer
#dynamic state;
- (id<CAAction>)actionForKey: (NSString *)key {
if( [key isEqualToString: #"state"] )
{
CABasicAnimation * bgAnimation = [CABasicAnimation animationWithKeyPath: #"backgroundColor"];
bgAnimation.fromValue = [self.presentationLayer backgroundColor];
bgAnimation.duration = 0.4;
return bgAnimation;
}
return [super actionForKey: key];
}
- (void)didChangeValueForKey: (NSString *)key {
if( [key isEqualToString: #"state"] )
{
const NSUInteger state = [self valueForKey: key];
UIColor * newBackgroundColor;
switch (state)
{
case 0:
newBackgroundColor = [UIColor redColor];
break;
case 1:
newBackgroundColor = [UIColor blueColor];
break;
case 2:
newBackgroundColor = [UIColor greenColor];
break;
default:
newBackgroundColor = [UIColor purpleColor];
}
self.backgroundColor = newBackgroundColor.CGColor;
}
[super didChangeValueForKey: key];
}
#end
Core Animation calls actionForKey: before updating the property value. It runs the action after updating the property value by sending it runActionForKey:object:arguments:. The CAAnimation implementation of runActionForKey:object:arguments: just calls [object addAnimation:self forKey:key].
Instead of returning the animation from actionForKey:, you can return a CAAction that, when run, creates and installs an animation. Something like this:
#interface MyAction: NSObject <CAAction>
#property (nonatomic, strong) id priorValue;
#end
#implementation MyAction
- (void)runActionForKey:(NSString *)key object:(id)anObject arguments:(NSDictionary *)dict {
ERAnimatablePropertyLayer *layer = anObject;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
id newValue = [layer valueForKey:key];
// Set up animation using self.priorValue and newValue to determine
// fromValue and toValue. You could use a CAKeyframeAnimation instead of
// a CABasicAnimation.
[layer addAnimation:animation forKey:key];
}
#end
#implementation ERAnimatablePropertyLayer
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
MyAction *action = [[MyAction alloc] init];
action.priorValue = [self valueForKey:key];
return action;
}
return [super actionForKey:key];
}
You can find a working example of a similar technique (in Swift, animating cornerRadius) in this answer.
You can store the old and new values in CATransaction.
-(void)setMyProperty:(float)value
{
NSNumber *fromValue = [NSNumber numberWithFloat:myProperty];
[CATransaction setValue:fromValue forKey:#"myPropertyFromValue"];
myProperty = value;
NSNumber *toValue = [NSNumber numberWithFloat:myProperty];
[CATransaction setValue:toValue forKey:#"myPropertyToValue"];
}
- (id <CAAction>)actionForKey:(NSString *)key {
if ([key isEqualToString:#"myProperty"]) {
CABasicAnimation *theAnimation = [CABasicAnimation animationWithKeyPath:key];
theAnimation.fromValue = [[self presentationLayer] valueForKey:key];
theAnimation.toValue = [CATransaction objectForKey:#"myPropertyToValue"];
// here you do something special.
}
return [super actionForKey:key];
}

How do I use UIPageViewController to load separate XIBs?

I'm delving into the new world of UIPageViewControllers and there are a lot of tutorials out there, however all of them seem to create one view, and then just use new instances of it with different content.
I'd really like to be able to create multiple XIBs and then just chain them together with the UIPageViewController but it's too new and I can't get my head around the way it works.
Well, here's a long answer that you should be able to copy and paste. (This code was adapted from Erica Sadun (https://github.com/erica/iOS-5-Cookbook))
First, create a new class of type UIPageViewController. Call it BookController. Now paste the following code in your .h file.
// Used for storing the most recent book page used
#define DEFAULTS_BOOKPAGE #"BookControllerMostRecentPage"
#protocol BookControllerDelegate <NSObject>
- (id) viewControllerForPage: (int) pageNumber;
#optional
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber;
#end
#interface BookController : UIPageViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
+ (id) bookWithDelegate: (id) theDelegate;
+ (id) rotatableViewController;
- (void) moveToPage: (uint) requestedPage;
- (int) currentPage;
#property (assign) id <BookControllerDelegate> bookDelegate;
#property (nonatomic, assign) uint pageNumber;
and in your .m file:
#define IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
#define SAFE_ADD(_Array_, _Object_) {if (_Object_ && [_Array_ isKindOfClass:[NSMutableArray class]]) [pageControllers addObject:_Object_];}
#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG) (([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? [THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] : nil)
#pragma Utility Class - VC that Rotates
#interface RotatableVC : UIViewController
#end
#implementation RotatableVC
- (void) loadView
{
[super loadView];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view.backgroundColor = [UIColor whiteColor];
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
#end
#pragma Book Controller
#implementation BookController
#synthesize bookDelegate, pageNumber;
#pragma mark Debug / Utility
- (int) currentPage
{
int pageCheck = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
return pageCheck;
}
#pragma mark Page Handling
// Update if you'd rather use some other decision style
- (BOOL) useSideBySide: (UIInterfaceOrientation) orientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation);
return isLandscape;
}
// Store the new page and update the delegate
- (void) updatePageTo: (uint) newPageNumber
{
pageNumber = newPageNumber;
[[NSUserDefaults standardUserDefaults] setInteger:pageNumber forKey:DEFAULTS_BOOKPAGE];
[[NSUserDefaults standardUserDefaults] synchronize];
SAFE_PERFORM_WITH_ARG(bookDelegate, #selector(bookControllerDidTurnToPage:), [NSNumber numberWithInt:pageNumber]);
}
// Request controller from delegate
- (UIViewController *) controllerAtPage: (int) aPageNumber
{
if (bookDelegate &&
[bookDelegate respondsToSelector:#selector(viewControllerForPage:)])
{
UIViewController *controller = [bookDelegate viewControllerForPage:aPageNumber];
controller.view.tag = aPageNumber;
return controller;
}
return nil;
}
// Update interface to the given page
- (void) fetchControllersForPage: (uint) requestedPage orientation: (UIInterfaceOrientation) orientation
{
BOOL sideBySide = [self useSideBySide:orientation];
int numberOfPagesNeeded = sideBySide ? 2 : 1;
int currentCount = self.viewControllers.count;
uint leftPage = requestedPage;
if (sideBySide && (leftPage % 2)) leftPage--;
// Only check against current page when count is appropriate
if (currentCount && (currentCount == numberOfPagesNeeded))
{
if (pageNumber == requestedPage) return;
if (pageNumber == leftPage) return;
}
// Decide the prevailing direction by checking the new page against the old
UIPageViewControllerNavigationDirection direction = (requestedPage > pageNumber) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
[self updatePageTo:requestedPage];
// Update the controllers
NSMutableArray *pageControllers = [NSMutableArray array];
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage]);
if (sideBySide)
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage + 1]);
[self setViewControllers:pageControllers direction: direction animated:YES completion:nil];
}
// Entry point for external move request
- (void) moveToPage: (uint) requestedPage
{
[self fetchControllersForPage:requestedPage orientation:(UIInterfaceOrientation)[UIDevice currentDevice].orientation];
}
#pragma mark Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber + 1];
return [self controllerAtPage:(viewController.view.tag + 1)];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber - 1];
return [self controllerAtPage:(viewController.view.tag - 1)];
}
#pragma mark Delegate
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSUInteger indexOfCurrentViewController = 0;
if (self.viewControllers.count)
indexOfCurrentViewController = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
[self fetchControllersForPage:indexOfCurrentViewController orientation:orientation];
BOOL sideBySide = [self useSideBySide:orientation];
self.doubleSided = sideBySide;
UIPageViewControllerSpineLocation spineLocation = sideBySide ? UIPageViewControllerSpineLocationMid : UIPageViewControllerSpineLocationMin;
return spineLocation;
}
-(void)dealloc{
self.bookDelegate = nil;
}
#pragma mark Class utility routines
// Return a UIViewController that knows how to rotate
+ (id) rotatableViewController
{
UIViewController *vc = [[RotatableVC alloc] init];
return vc;
}
// Return a new book
+ (id) bookWithDelegate: (id) theDelegate
{
BookController *bc = [[BookController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
bc.dataSource = bc;
bc.delegate = bc;
bc.bookDelegate = theDelegate;
return bc;
}
This Class can now be used to control any book you create in any project, and for multiple books in a single project. For each book, create a delegate UIPageViewController with the #interface:
#interface NameOfBookController : UIPageViewController <BookControllerDelegate>
In the .m file of this delegate, include:
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
CGRect appRect = [[UIScreen mainScreen] applicationFrame];
self.view = [[UIView alloc] initWithFrame: appRect];
self.view.backgroundColor = [UIColor whiteColor];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// Establish the page view controller
bookController = [BookController bookWithDelegate:self];
bookController.view.frame = (CGRect){.size = appRect.size};
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
// Add the child controller, and set it to the first page
[self.view addSubview:bookController.view];
[self addChildViewController:bookController];
[bookController didMoveToParentViewController:self];
}
Then add:
- (id) viewControllerForPage: (int) pageNumber
{
// Establish a new controller
UIViewController *controller;
switch (pageNumber) {
case 0:
view1 = [[FirstViewController alloc] init];
view1.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
controller = view1;
//rinse and repeat with each new view controller
break;
case 1:
//etc.
break;
default:
return nil;
break;
}
return controller;
}
For the record, this code is not memory-safe. If using ARC, add in your #autoreleasePool{}; if not, don't forget your retain/release cycle.
I hope this helps!
This article shows how to create an app using UIPageViewController with custom viewcontrollers for each page: http://www.informit.com/articles/article.aspx?p=1760500&seqNum=6