Showing posts with label ios. Show all posts
Showing posts with label ios. Show all posts

Monday, September 14, 2015

My dive into KVO (Key Value Observers)

I've played with KVO for a while but was always afraid of it. Most people are I find. whenever I brought it up, people would say things like "dangerous", "spaghetti code", "it will crash your app and be impossible to debug" or just plain "messy". It is a powerful feature, but it can so easily become more of a problem than it is worth.

KVO (Key Value Observer) lets you listen for changes on an object's property. Whenever that property changes, you will get an observation notification. This can be very helpful when you are waiting for something to get updated but if you aren't careful you can really mess things up.

A few examples of things to watch out for;
-cycling, if you are listening for one property, it changes, you change your data as a result and then you changes trigger another KVO that changes other properties, which changes the properties you were listening to in the first place, bad stuff will happen.

-crashing on removal. If you try and remove the observation of a property when you were not even listening to it or worse yet, if you already stopped listening to it, it will crash your app. (People like to put @try @catch around that to avoid it but that is a lazy way of fixing it, create a bool to let you know if you already stopped listening.)

-crashing on not correct listening. When you start observing properties, it will run the observation method. This will be run on everything. If you don't account for one of the items you are listening to, or even worse, something else tells you to listen to something you are not aware of, when it goes through and doesn't do anything, it will crash your app.

-inconsistency. If you are not aware of other people KVOing your properties, you could be doing something to your properties (which you are entitled to of course because it is yours) and you might be triggering KVOs all over the place for the wrong reasons which is bad on many levels.

I am currently working with someone whom got over the hurtles and loves KVO, Here are some tips he gave me to prevent much of this from happening.

+ Try and only start and stop your KVO on the init and dealloc. If you need to stop or start listening outside of these, create a bool to mark whether or not you are currently listening.

+ Use NSCopying. This will prevent possible cycling on KVOs

 + Don't listen to overly changeable data. Any property that is mutable is probably changing way more than you need to listen for so stick to the immutable properties

+ Don't bother with contexts. keypaths should be unique enough for figuring out what you are listening to. Context add an unnecessary level of complexity to your code. If you are listening to multiple items with the same keypath, you have other issues.

So now that you are hopeful, or at least willing to try something. Here are the basics on how to implement it;

- (id)init {
    if (self = [super init]) {        
        [self addObserver:self forKeyPath:@"activeStation.playbackStatus" options:NSKeyValueObservingOptionNew context:nil];
        [self addObserver:self forKeyPath:@"activeStation" options:NSKeyValueObservingOptionNew context:nil];
    }
    return self;

}

- (void)dealloc {
    [self removeObserver:self forKeyPath:@"activeStation.playbackStatus"];
    [self removeObserver:self forKeyPath:@"activeStation"];
}


#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if ([keyPath isEqualToString:@"activeStation.playbackStatus"]) {

        }
    } else if ([keyPath isEqualToString:@"activeStation"]) {

    }
}

and that is about it. When should you use KVO? Here is a link that my friend found that helps let you know. As you can see, it is limited on where to KVO but never handicap yourself just because of fear!

Thursday, July 23, 2015

iOS KVO explained or How Key Value Observations work

There are so many ways to transfer data between objects. delegate, blocks, notifications, etc. Understanding them and how to use them properly is one of the most critical aspects of developing apps. Which ones should I used and why is very important. With that being said, here is one more that I've used many times before but always seemed to avoid really understanding, until now. KVO.

Key-Value Observing (KVO) is another way to learn about other objects without them really offering it up easily. The basis of this technique is if you want to know when a value of a different object changes you can create a KVO to detect and inform you of when this actually happens. Many apple APIs use this like AVPlayer. Personally I am not a big fan but I can appreciate why it is used.

Lets get down to it. Here is the declaration;

 [object addObserver:(NSObject *)anObserver
         forKeyPath:(NSString *)keyPath
            options:(NSKeyValueObservingOptions)options
            context:(void *)context];

-object is the object you want to observe the change on
-anObserver should whom you want to be listening, usually self.
-keyPath is the name of the property you want to listen for. Example is when a label text changes you would put in @"text"
-options is for when it actually changes, what do you want back. There are 4 options of which you can choose multiple;    
    NSKeyValueObservingOptionNew = 0x01, when you want what the new value is
    NSKeyValueObservingOptionOld = 0x02, when you want what the old value was
    NSKeyValueObservingOptionInitial = 0x04, when you want to know before it happens
    NSKeyValueObservingOptionPrior  = 0x08 when you want to know after it happens (can't get the old one then)
-context is a unique identifier that you can reference when the call back method is actually called. I needs to be unique and nothing else. So people like to get creative with this sometimes. Most of the time I see something like this;


static void *AVPlayerDemoPlaybackViewControllerStatusObservationContext = &AVPlayerDemoPlaybackViewControllerStatusObservationContext;

This is just a pointer that references itself. It is small and unique so it fits perfectly. And when you want to find it, you can do so easily. 

Now that you are listening, now what? How do I actually do something? Well you need to run the actually method it plans to run when it needs to of course. It is;

- (void)observeValueForKeyPath:(NSString*) path
                      ofObject:(id)object
                        change:(NSDictionary*)change
                       context:(void*)context

-path is the string you want
-object is the actual object 
-change is a dictionary of the items you requested
-context is that unique context you created above

Since this method is called for all value changes you should isolate what you are looking for each time by referencing path, context and/or object. Once you have done that, grab the change dictionary and see what was actually changed. 

Note: Most of the time you want to compare the new and old one when you get them to make sure it was not actually just updated with the same thing twice.

   if (context == AVPlayerDemoPlaybackViewControllerStatusObservationContext){
        AVPlayerItemStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
        self.streamStatusType = StreamStatusTypeUnknownState;
        switch (status){
            case AVPlayerItemStatusUnknown:{
                self.streamStatusType = StreamStatusTypeUnknownState;
                break;
            }
            case AVPlayerItemStatusReadyToPlay:{
                self.streamStatusType = StreamStatusTypeReadyToPlay;
                break;
            }
            case AVPlayerItemStatusFailed: {
                self.streamStatusType = StreamStatusTypeFailed;
                break;
            }
        }
     }

And that is about it!

Bonus note: removing the observer can actually crash the app randomly is seems so it is good practice to add a @try @catch around it just in case.

        @try {
            [self.playerItem removeObserver:self forKeyPath:@"status"];
        }
        @catch (NSException *exception) {
            
        }
        @finally {
            

        }


References:
Apple KVO intro
Apple KVO example


Wednesday, July 22, 2015

How to make a 3D planet in xcode using SceneKit

For some reason I've always wanted to create a spinning 3D globe but never had the time or ability. I decided last weekend to see if I could find someone or some way to make one. I checked all the usual places but the few I found were either overly complicated, way out of date, or no longer worked. So I decided to put my hat in the ring. I thought this might also be a good way to play with scenekit since I have never touched it before.

Wow. It worked better than I could ever imagine. Here is how to make the whole world in a few steps and about 15 lines of code.

First create a new project using scene kit. I like objective-c but you can do whatever you want with the language. You should be able to run it right away and see an airplane that you can move around the screen in 3 dimensions. Don't worry about that.

Here is the quick code;

-(void)worldSetup{
    SCNView *scnView = (SCNView *)self.view;
    
    myScene = [[SCNScene alloc] init];
    
    SCNSphere *planetSphere = [SCNSphere sphereWithRadius:5.0];
    SCNNode *sphereNode = [SCNNode nodeWithGeometry:planetSphere];
    [myScene.rootNode addChildNode:sphereNode];

    
    SCNMaterial *corona = [SCNMaterial material];
    corona.diffuse.contents = [UIImage imageNamed:@"earth"];
    corona.specular.contents = [UIColor colorWithWhite:0.6 alpha:1.0];
    corona.shininess = 0.5;
    [planetSphere removeMaterialAtIndex:0];
    planetSphere.materials = @[corona];
    
    // create and add a light to the scene
    scnView.autoenablesDefaultLighting = YES;
    
    // create and add a camera to the scene
    scnView.allowsCameraControl = true;
    
    mySphere = planetSphere;
    
    scnView.scene = myScene;
}

earth
in celebration of seeing pluto,
I added some love

Here is the alternate slightly more custom code.

-(void)worldSetup{
    SCNView *scnView = (SCNView *)self.view;
    
    myScene = [[SCNScene alloc] init];
    
    SCNSphere *planetSphere = [SCNSphere sphereWithRadius:5.0];
    SCNNode *sphereNode = [SCNNode nodeWithGeometry:planetSphere];
    [myScene.rootNode addChildNode:sphereNode];

    
    SCNMaterial *corona = [SCNMaterial material];
    corona.diffuse.contents = [UIImage imageNamed:@"earth"];
    corona.specular.contents = [UIColor colorWithWhite:0.6 alpha:1.0];
    corona.shininess = 0.5;
    [planetSphere removeMaterialAtIndex:0];
    planetSphere.materials = @[corona];
    
    // create and add a light to the scene
    SCNNode *lightNode = [SCNNode node];
    lightNode.light = [SCNLight light];
    lightNode.light.type = SCNLightTypeAmbient;
    lightNode.light.color = [UIColor colorWithWhite:0.37 alpha:1.0];
    lightNode.position = SCNVector3Make(0, 50, 50);
    [myScene.rootNode addChildNode:lightNode];
    
    SCNNode *omniLightNode = [SCNNode node];
    omniLightNode.light = [SCNLight light];
    omniLightNode.light.type = SCNLightTypeOmni;
    omniLightNode.light.color = [UIColor colorWithWhite:.70 alpha:1.0];
    omniLightNode.position = SCNVector3Make(0, 40, 40);
    [myScene.rootNode addChildNode:omniLightNode];
    
    // create and add a camera to the scene
    scnView.allowsCameraControl = true;
    
    // create and add a camera to the scene
    SCNNode *cameraNode = [SCNNode node];
    cameraNode.camera = [SCNCamera camera];
    cameraNode.position = SCNVector3Make(0, 0, 20);
    [myScene.rootNode addChildNode:cameraNode];
    
    mySphere = planetSphere;
    
    scnView.scene = myScene;
}

You still need to get an 'earth' texture from the internet somewhere, should be easy to find, probably from NASA. And that is it!

Here is a link to the app in github. https://github.com/Darkin/3DPlanet

Tuesday, July 14, 2015

Helpful CocoaPods

The classic and helpful AFNetworking
Github - CocoaPod pod "AFNetworking", "~> 2.0"

The robust and powerful JSON data network restful RESTKIT
Github - CocoaPod pod 'RestKit', '~> 0.24.0'
                                # Testing and Search are optional components
                                pod 'RestKit/Testing', '~> 0.24.0'
                                pod 'RestKit/Search',  '~> 0.24.0'

The nice JSON to/from NSDictionary template MANTLE
Github - CocoaPod  pod 'Mantle', '~>2.0.2'

The pretty much required facebook 1 2 3
Github - CocoaPod 'FBSDKCoreKit', 'FBSDKLoginKit', 'FBSDKShareKit' .

The ever helpful image downloader and cacher SDWebImage
Github - CocoaPod pod 'SDWebImage', '~>3.7'

Thursday, May 28, 2015

Random Storyboard Autolayout Situational Help

I'm just going to put random helpful links here that explain how to perform certain tasks in the xcode storyboard that might be helpful.

- Spreading out Items (like UIButtons) evenly. Helps to put them all in a separate UIView as well so you can move them all together.

- Simple Getting Started Tutorial with font sizing. Helps understand constraints that only work in certain devices as well as a nice font sizing section.

-How to unwind a segue with the push of a button. Frustrating if you don't know it. Explains how to setup and properly close a segue from any parent view controller. There is a programmatic version here.


Tuesday, May 26, 2015

Using CocoaPods and setting up Facebook SDK 4.1 in an xcode workspace in iOS8

Cocoapods are the new external framework. They are easy to setup, easy to update and easy to incorporate... one you have done it a few times. The most common external framework people use is the FacebookSDK. I will walk you through the basics of setting up a xcode workspace that incorporates the new facebookSDK which are now three parts; 'FBSDKCoreKit', 'FBSDKLoginKit', and 'FBSDKShareKit'.

I will probably go through some basic facebook usage in another post... maybe.

First you need to setup cocoapods. If you have not done so already, go into a terminal from your launchpad/utilities and type;
 $ sudo gem install cocoapods
This should use your integrated Ruby and setup cocoapods so you can use them whenever you want. It will most likely ask for your computer password and give you a warning but that is normal. Your mac should then  install a bunch of files and you are off to the races.

Next you will need to setup a work location. To be clean, create a new folder for your new app.
$ mkdir NewApp
$ cd NewApp
$ touch Podfile

You should be in your new app folder and in that folder should be an empty file called Podfile. This file is the instructions for cocoapods on what pods you want installed for this particular app. Since we want the app to have the new facebook pods installed, open up the Podfile in whichever prefered editor you want and copy this in;

source 'https://github.com/CocoaPods/Specs.git'
xcodeproj 'NewApp'
platform :ios, '7.0'
#Facebook Stuff
pod 'FBSDKCoreKit', '~> 4.1'
pod 'FBSDKLoginKit', '~> 4.1'
pod 'FBSDKShareKit', '~> 4.1'

What this does; The first line is to tell Cocoapods where on the internet you wish to look for the pods. The second line says what project you want your pods to work with. The third line says what target you want to setup. The lines with a # at the beginning are just comments and the last three lines are the actual facebook pods you wish to install into your workspace. Close this up and open up xcode.

In xcode, create a new project called NewApp (or whatever you decided to call it in the other areas) and put it in the same directory as the Podfile. Now go back to your terminal session and type;
$pod install

If everything was done correctly, you should see some green command lines installing the facebook pods and then the folder should also have a new xcworkspace file. Open up that file and you should see both your project and and 'Pods' in your workspace directory in xcode.

And you are done!

Why do this? Well, not only can you copy and paste this into all future projects but you can also update the pods at any point by just typing 'pod install' or 'pod update'. You can also create your own local pods and do the same thing. The way to do that is just specify a local directory for the pod you wish to incorporate;


#pod 'D2TSounds', :path => '/Users/yourname/Documents/CocoaPods/D2TSounds'

You might need to do a little setup for the local files but if you have several items that you use a lot, this will save you a lot of time in the long run.

Helpful links;
Cocoapods initial setup
Cocoapod pod link page
Facebook Login
Facebook Sharing
Facebook App Links
Facebook Graph API


Tuesday, February 10, 2015

Using blocks in objective-c iOS7 and iOS8

blocks are a godsend and still people are reserved in using it. Perhaps it is because they don't know how to set it up. Here is a quick example on how to set it up.

In the .h file of whatever you are creating, make a new typedef for the block;

typedef void(^myCompletion)(BOOL complete);

This line shows that this is a "myCompletion" block and has one value going into it which a bool called complete. 

Next in the .h file create the method you want with a completion block;

-(void)doMyStuff withCompletion:(myCompletion)compBlock;

Now all you have to do is create it in your .m file;

-(void)doMyStuff withCompletion:(myCompletion)compBlock{
     //do the stuff you want to do 
     if (compBlock != nil) {
         compBlock(YES);
     }
}

This will do the method and then check if the compBlock is nil (protection) and if it is not nil, perform that block of functions as well. 

Technically you can do the block anywhere in the code, I am just using completion as an example since most of the time, that is where it is used. 

Now you actually want to use this method? Simple;

[myMethod doMyStuff withCompletion:^(BOOL complete) {
     if (complete){

          self.navController.topViewController.navigationItem.titleView = logoView; 
     }
}];

and there you have it. 

if you want another version, you can look here.
Stanford also has a nice video about using existing blocks here.


Sunday, September 14, 2014

Grabbing call notifications in iOS7

Looks like there is not much in the way of documentation for how to do this so I am going to create one here.

Sometimes people get calls on their phone, I know this is crazy but it can happen. What you should do about it is up to you but how can you let your app know when this happens? Well I can help with that.

in your .h file, add the following.


#import <CoreTelephony/CTCallCenter.h>

@property (nonatomic, strong) CTCallCenter* callCenter;

you need to set the property as if you don't have anything holding on to the connection, it will disappear on your. Gotta love ARC!

in the .m file, here is your master method;

#pragma mark - phone call interrupts
-(void) setupPhoneNotification{
    float static playerVolume;
    self.callCenter = [[CTCallCenter alloc] init];
    self.callCenter.callEventHandler=^(CTCall* call)
    {
        
        if(call.callState == CTCallStateDialing)
        {
            //The call state, before connection is established, when the user initiates the call.
            NSLog(@"Call is dailing");
        }
        if(call.callState == CTCallStateIncoming)
        {
            //The call state, before connection is established, when a call is incoming but not yet answered by the user.
            NSLog(@"Call is Coming");
        }
        
        if(call.callState == CTCallStateConnected)
        {
            //The call state when the call is fully established for all parties involved.
            NSLog(@"Call Connected");
        }
        
        if(call.callState == CTCallStateDisconnected)
        {
            //The call state Ended.
            NSLog(@"Call Ended");
        } 
    };
}

And there you have it. Do whatever you need to do in the block, possibly send a trigger event or notifcation and then you are off to the races. Have fun!

Monday, January 6, 2014

iOS converting most audio files into iOS friendly audio files

iOS is both very generous and very picky when it comes to audio. It can play almost anything without issue but when you want to play audio in certain ways, it just won't work. Here is a quick way to convert your existing audio files to iOS friendly .caf files;

1) on the folder with your sound files in it, select it an choose 'copy'
2) press "Command-space" to open up the fast launch option on your mac
3) type "terminal" to open up a command terminal session
4) type "cd " and then "command-v" to paste the location of the directory and press enter to go to that directory
5) you can press "cd" or "ls" to confirm you are in the right directory, you should see the files you want to change there
6) type "afconvert -f caff -d LEI16@44100 -c 1 in.wav out.caf"
or "afconvert in.mp3 out.caf -d ima4 -f caff -v" with "in" being your file you want to change and "out" being the file you want to change it to.

And you are done! Don't forget to copy the new files into your application. 

Wednesday, July 24, 2013

Setting and Testing localizations in iOS 6

To Set:
http://stackoverflow.com/questions/5349066/how-to-localize-my-app-with-xcode-4

7) in your code use  NSLocalizedString(@"TEST", @"test") where the first arg is the string and the second arg is the description (optional can be nil)
1) Create strings file
2) call it "Localizable.strings"
3) fill the localizable.string with all the english terms you use in the app "TEST" = "TEST";
4) file inspector "Make Localized"
5) + add localization you want
6) add only the localizable.strings file to the list
8) translate and smile

To Test:
http://useyourloaf.com/blog/2013/07/22/using-launch-arguments-to-test-localizations.html

1) go into the current Scheme
2) goto arguments
3) add "
-AppleLanguages (Russian)" without quotes
4) click on and off to test the various languages and smile

Thursday, July 11, 2013

How to create a subview that will be a popup on large devices and full screen on small ones in iOS 6 with ARC

Universal apps are becoming more and more necessary today. They are becoming more complicated as well. At least they don't hundreds of different size screens to deal with like android.

First you should have a subview that is the size of a standard smaller screen (itouch, iphone). We will call this "SearchViewController".  You might want to use a delegate to interact with the views but we won't worry about that in this exercise.

in the SearchViewController.m;

-(void) closeView{
    //all keyboard items should have "resignFirstResponder" attached to it here just in case
    [self.view removeFromSuperview];
}

in the MainViewController.h;

#import "SearchViewController.h"

@interface MainViewController <UIPopoverControllerDelegate>{
    SearchViewController *searcher;
}

in the MainViewController.m;

-(void)OpenSearch{
    static bool isSearchOpen = FALSE;
    //open up the subview for the search function. Open up as a mini view for the ipad and a full window view for the iphone.
    if (isSearchOpen == FALSE){
        isSearchOpen = TRUE;
        if (searcher == nil){
            searcher = [[SearchViewController alloc]init];
            searcher.delegate = self;
            if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
                CGRect frame = searcher.view.frame;
                searcher.view.frame = CGRectMake(0, 0, frame.size.width, frame.size.height-88);
            }
        }
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
            //iphone
            [self addChildViewController:searcher];
            [self.view addSubview:searcher.view];
            [searcher didMoveToParentViewController:self];
        }else{
            //ipad
            popSearch = [[UIPopoverController alloc] initWithContentViewController:searcher];
            popSearch.popoverContentSize = searcher.view.frame.size;
            popSearch.delegate = self;
            [popSearch presentPopoverFromRect:CGRectMake(self.view.frame.size.width - 5,0, 10, 10) inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
        }
    }else{
        isSearchOpen = FALSE;
        [self closeSearch];
    }    

}

-(void)closeSearch{
    //cancels the search and closes the search view
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
        [searcher.view removeFromSuperview];
    }else{
        [popSearch dismissPopoverAnimated:YES];
    }
    isSearchOpen = FALSE;
}

Done :)

Wednesday, June 12, 2013

Different ways to differentiate between devices in iOS

There are many ways to figure out how to tell which device you are using, some are specific, some are general. They all serve their purpose. Here are some examples;

If you have a universal app and want to use a different nib for the ipad or iphone;
    NSString *nibName = [NSStringFromClass([self class]) stringByAppendingString:([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)? @"-iPhone" : @"-iPad"];
    self = [super initWithNibName:nibName bundle:nil];
* note be sure and name you nibs correctly.

If you have some specific code the you want to perform on either the iphone or ipad;
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone){
    //iphone code
}else{
    //ipad code
}

If you want a quick definition change depending on if it is an ipad or iphone;
    int total = ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)? iPhone_Total :iPad_Total;

Thursday, May 2, 2013

iOS Creating Custom Delegates in iOS6 with ARC

So you want two Objects to talk to each other more than a little bit? Here is what to do by example.  Lets say we have our MainViewController and a new SearchViewController that you want a delegate attached to it. This is all you need to do.

In SearchViewController.h
@protocol SearchViewDelegate <NSObject>
@optional
@required
-(void)didAskToSearch:(NSString*)string;
@end

@interface SearchViewController : UIViewController {
}
@property (nonatomic, assign) id <SearchViewDelegate> delegate;
@end

optional has non-required methods and required of course has required methods. 

In SearchViewController.m;
    [_delegate didAskToSearch: [self searchRequestDetails]];

this is put anywhere in the code when you want it to send the information back to the other object. 

In the MainViewController.h;
@interface MainViewController : UIViewController <SearchViewDelegate> {
}

In MainViewController.m;
SearchViewController *searcher = [[SearchViewController alloc]init];
searcher.delegate = self;

#pragma mark - SearchViewControllerDelegates
-(void)didAskToSearch:(NSString*)string{
     //do whatever you want with the string results
}

When creating the object, make sure the tell the delegate you have those methods. Then you just have to create the methods as per it is required. 

Helpful link
http://stackoverflow.com/questions/12020539/how-to-make-custom-delegate-in-ios-app