[TIL ObjC.005];

To test Quick Action shortcuts on your 3d touch device you need to be able to access LaunchOptions on your will/didFinishLaunchingWithOptions: method. If you are using Xcode's default settings you won't get what you need in LaunchOptions. The best way to fix that is to change the run scheme for your app. 

Hold Option and press the play button. You will get window with some build options. Change Launch from "Automatically" to "Wait for executable to be launched" 

After hitting run your app will be copied to the device and will not start debugging until you physically tap your app icon. You can now launch the app via your shortcuts and will get what you need in LaunchOptions.

ps. Be careful when committing this change. Could (Did) confuse co-workers. 

[TIL ObjC.004];

Prevent image bleed past layer border.

1. Create UIVIew with a UIImageView inside. Constraints on the UIImageView should be -1 from all side of the UIVIew.

2. Add border properties to the UIView. 

    cell.avatarContainerView.layer.borderWidth = 2.0;
    cell.avatarContainerView.layer.borderColor = [UIColoe whiteColor].CGColor;
    cell.avatarContainerView.layer.cornerRadius = 
        cell.avatarContainerView.bounds.size.height / 2;

Optional:

Add .layer.shouldRasterize = YES; for performance gains.

 

[TIL ObjC.003];

To set colors in an object's runtime attributes in Interface Builder

1. Create a category for CALayer

2. Add the following setter method and make it public

      - (void)setColorFromUIColor:(UIColor *)color {
          self.borderColor = color.CGColor;
      }

. In the object's runtime attributes add "layer.colorFromUIColor"

source

[TIL ObjC.002];

Defining and calling blocks:

1. http://fuckingblocksyntax.com

2. call 'blockName()' within the block where you want the delayed code to run

3. when calling the method define what is run inside of that block

4. call block off of main thread with [[NSOperationQueue mainQueue] addOperationWithBlock:^{}];

[TIL ObjC.000];

When setting text on a button do not use button.title.text instead use --

[button setTitle:@"" forState:UIControlState];

Bonus:

To resize a button's label --

        button.titleLabel.adjustsFontSizeToFitWidth = YES;

        button.titleLabel.minimumScaleFactor = .5;

Page Controls

I am working on converting a client's app to auto layout. Currently the views are set up in code using hard set CGPoints to place objects in the view. This worked okay on the 3.5 inch screens when the app initially launched but Apple has moved on from that screen size, so we have to as well. 

One piece of the puzzle is setting up a new page controller. Page controllers are the little dots you see on your home screen and in various apps with more than one horizontal view. Those dots tell you how many pages there are total and which one you are currently on. 

Most of the configuration of the page control can be done in interface builder without touching code except for the last little bits. Here are the steps I took to make this page control work.

First -- drag over your page control from the list in the Utilities area and constrain it in your layout.

Next create two outlets from your new page control and add the UIScrollViewDelegate to your classes interface file.

@interface Main : UIViewController <UIScrollViewDelegate>

@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (strong, nonatomic) IBOutlet UIPageControl *pageControl;

* Don't forget to connect the delegate to the scroll view. * (This is starred because I forgot to do this initially and it took some extra time to find.)

Next you can configure the scroll view and page control in interface builder.

'# of Pages' is the total number of pages (dots) you want shown. 'Current' is which dot you want to start on. This count starts from 0 so if you want to start on the first dot make sure you use '0' not '1'. 

Also make sure your scroll view have scrolling and paging enabled.

Lastly the easy part, coding it up.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{
    CGFloat pageWidth = self.scrollView.frame.size.width;
    int page = floor((self.scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
    self.pageControl.currentPage = page;
}

Using the delegate method 'scrollViewDidScroll' you can control which dot is highlighted once the user scrolls more than 50% of the scroll view. If you have a different number of pages you'll need to edit the code for your own use.

 

Sources:

iOS Developer Library -- Page Control

iOS Developer Library -- UIPageControl Class Reference

Page Control for Switching Between Views

 

Fixing the Jambox Bluetooth Problem

Months ago my (original) Jawbone Jambox started sounding terrible. My significant other has a tendency to blast music and has blown a few speakers so we immediately decided she broke it. We put the speaker in a drawer and forgot about it. Well now that the weather is nice and we have been spending more time outside. I wanted to see what I could do to get the Jambox working again.

First thing was to figure out exactly what was wrong. I wanted to start off by checking to see if one of the speakers was actually blown.

To get the device open you need to start by removing the rubber from both the top and bottom of the unit. It isn't glued down or anything so carefully slide a credit card or anything thin into the space between the rubber and the grill to pry those pieces off.

The next step is to get the grill off. Start by removing the six screws from the top of the device. On the side with on/off switch you can see the grill seam. At the top and bottom edges of that grill seam are two metal clips that you will need to pry up. I used a very small flathead screwdriver. It is a little out of focus but you can see the bottom clip in this image. Once those clips are free you'll need to slide the grill up a fraction of an inch to release the tabs that run along the bottom edge of the grill. 

IMG_0489.png

Once you have the grill off remove the eight long screws from the back of the unit.

Now you should be able to careful pull the front and back pieces from each other. You need to do this very carefully as there are two ribbon cables that run between the two pieces. You do not want to tear either of them. 

Yours should look something like this. 

IMG_0483.png

I tested the speakers by carefully disconnecting the speaker cable (the twisted red and black wires) and testing them with a multimeter. Both of the speakers were responding to the multimeter so neither were blown (whoops, sorry Elysia). 

Next step was to figure out why the device sounded so bad. I noticed that when I had a cable connecting my audio source directly to the Jambox it sounded good. It was only when I was going over bluetooth that I had issues. So I did some googling and found that these devices are know to have an issue with the aux in bypass not letting the bluetooth audio work.

After poking the aux in jack with a small screwdriver I found that the bluetooth sounded almost as good as a hard line cable when I pressed a small metal tab down on the aux in jack. This is the bypass that doesn't work correctly. 

I got my soldering gun and soldered a small wire from that tab to a solder spot on the device board right below the tab. 

IMG_0482.png

Lastly I put a small piece of electric tape over it for protection.

The wire fixes the bluetooth issues but it does bypass the aux in jack completely so that no longer works. Unfortunate but since I always use this wirelessly it was something I was okay with.

Now just put it back together and you're back in business. 

 

 

Sending Custom Strings to UIActivityTypes (whhhhaaaaaaaaa)

You want to send different messages to twitter, facebook, and email via your app's share sheet options but how?

Well the secret is a custom delegate of UIActivityItemProvider. 

UIActivityItemProvider acts as a proxy for data to get to an active view controller. It is often used when you know what you want to send but it isn't ready yet, like sending a video to Youtube. The placeholder you create when alloc/init'ing the UIActivityItemProvider will be empty until the real object is ready which will then be used in replacement.

So what if you want to send different text to the different share options...

First create a custom subclass of UIActivityItemProvider:

.h

//
//  CustomItemUIActivityItemProvider.h
//  UIActivityItemProviderStuff
//
//  Created by Ian Smith on 4/9/15.
//  Copyright (c) 2015 Poop. All rights reserved.
//

#import <UIKit/UIKit.h>

@class FISDonorsChooseProposal;

@interface CustomItemUIActivityItemProvider : UIActivityItemProvider

-(id)initWithProposal:(Proposal *)proposal andPlaceholder:(NSString *)text;

@property (strong, nonatomic) Proposal *proposal;

@end

In the .m we need a customer initializer and to override the 'item' method that defines the text that is going to go to the different share options a user can choose. The sharing options are populated by 

//
//  CustomItemUIActivityItemProvider.m
//  
//
//  Created by Ian Smith on 4/9/15.
//  Copyright (c) 2015 Poop. All rights reserved.
//

#import "CustomItemUIActivityItemProvider.h"
#import "Proposal.h"

@implementation CustomItemUIActivityItemProvider

-(id)initWithProposal:(Proposal *)proposal andPlaceholder:(NSString *)text {
    self = [super initWithPlaceholderItem:text];
    
    if (self) {
        _proposal = proposal;
    }
    return self;
}


-(id)item
{
    NSString *formattedURL = [self.proposal.proposalURL componentsSeparatedByString:@"?"][0];
    NSString *shareText = [NSString stringWithFormat:@"This is the standard text string"];

    if (self.activityType == UIActivityTypeMail ) {
        return [NSString stringWithFormat:@"%@", shareText];


    } else if (self.activityType == UIActivityTypePostToTwitter) {
        NSString *tweetText = [NSString stringWithFormat:@"Check out my project %@ - %@", self.proposal.title, formattedURL];
        return [NSString stringWithFormat:@"%@", tweetText];
   

} else if (self.activityType == UIActivityTypeMessage)  {

       NSString *imessageText = [NSString stringWithFormat:@"Check out my project %@ - %@.\n &@", self.proposal.title, formattedURL,self.proposal.formattedText];
        return [NSString stringWithFormat:@"%@", imessageText];


    } else if { (self.activityType == UIActivityTypePostToFacebook) {

NSString *fbText = [NSString stringWithFormat:@"Hey Facebook friends, %@ \n\n\n %@", self.proposal.facebookText, formattedURL,];
        return [NSString stringWithFormat:@"%@", fbText];
}

@end

And to implement on the view controller --->

Add <UIActivityItemSource> as a delegate to your VC

And create an action -

- (void)shareTapped {
    CustomItemUIActivityItemProvider *customItem = [[CustomItemUIActivityItemProvider alloc] initWithProposal:self.proposal andPlaceholder:@"Placeholder Text"];


    UIActivityViewController *activityVC = [[UIActivityViewController alloc] initWithActivityItems:@[customItem] applicationActivities:nil];


    [self presentViewController:activityVC animated:YES completion:nil];
}

And with that for each selection that a user makes when sharing from your app the text they share will be different. 

Pull to Refresh Fancyness

Setting up Pull to Refresh in iOS is easy! Super easy for very fancy results.

Step 1 -> Enable Refreshing in your UIViewController's Attribute tab.

Step 2 -> Using the Assistant Editor Ctrl-Drag the Refresh Control to your View Controller

Step 3 -> Create your method to make something happen when view is pulled to refresh.

*important* make sure that you call 'endRefreshing' on the sender to make the spinner disappear. 

Objective-C Resources

Learning a programming language is hard, especially difficult if you try to do it in a vacuum. There are lots of great resources online and offline. I have compiled a list that will helpfully help both of us. 

Stackoverflow

The biggest resource that a developer of any language is StackOverflow. It is a great community built around a question and answer site. The site was launched in 2008. In the last 7 years lots and lots of questions have been asked and answered so it is most likely you will be able to find the answer out to any issues a beginner will run into. 

Search Engine / Google

The next great resource is the general internet. There are an endless amount of blog posts, ObjC resources, and and tutorials that have been posted around the net. A majority of the results that come back will be from StackOverflow but you'll also find great blog posts (especially those written by Flatiron students!).

IRC

Old heads will remember the days of internet relay chat. Think Slack before the internet was cool. There are tons of channels on all different networks where developers hang out and help one another. A few of the popular ones are on EFnet. They are #learnprogramming, ##objc, #iphonedev, and #cocoa-init. 

Reddit

Reddit has a few programming subreddits that can be very helpful as well. /r/iosprogramming is a great resource for iOS specific questions but there are also more general programming reddit like /r/dailyprogrammer where weekly challenges of varying levels are posted that can be solved in any language. It is can be helpful to review other people's code to learn tidbits. 

Blogs

Here is a list of Objective-C blogs that have tutorials and walk throughs. When learning something complex like a programming language it can be helpful to read over different people's interpretations. While one person's explanation might not click for you someone else's may.

http://techotopia.com
http://code.tutsplus.com
http://nshipster.com
http://rypress.com
http://raywenderlich.com

CocoaHeads

Cocoaheads is an international group of Objective-C & Swift developers. Each groups holds their own (monthly) meetings. Often people will demo apps or projects they work on as well as have a presentation about a particular topic related to the Apple developr world.

Apple's Developer Library

Lastly the most dry but most informative resource is Apple's own documentation. It is the best place to find methods that have been deprecated as well and new features that you may not have known about. Not only do they document the language but also have their own their own guide to walk you through the first step of installing Xcode all the way to writing full featured apps.

 

 

 

I fuck up NSNumber & NSInteger a lot.

If you are new to Objective-C like me you may see this error quite a bit -

<pre><code class=language-objectivec>
Invalid operands to binary expression ('NSInteger' (aka 'long') and 'NSNumber *')
</code></pre>

It means that you aren't using the correct datatype. No worries though we'll get that cleared up right now.

NSNumber
    A NSNumber is a subclass of NSValue that offers a value as any C scalar (numeric) type. It defines a set of methods specifically for setting and accessing the value as a signed or unsigned char, short int, int, long int, long long int, float, or double or as a BOOL. (Note that number objects do not necessarily preserve the type they are created with.) It also defines a compare: method to determine the ordering of two NSNumber objects.
    
    That is a whole lot mumbo jumbo that might not be totally clear to you yet. Here is what you need to know --
    
    - NSNumbers are an object datatype so you will use *pointers with them.
    - If you are going to be doing any math on the numeric value don't use NSNumber
    - NSNumber objects can added to a collection like an array or dictionary
    - You can also skip conversions by wrapping your NSNumber object with @() to do math

double x = 4.0;
NSNumber *result = @(x * .10);
NSLog(@"%.2f", [result doubleValue]);

NSInteger
    NSIntegers are not an object. Instead they are a wrapper for the long (signed and unsigned) integer primitive datatype. One of the reasons Apple added NSInteger to Objective-C was so make your life easier dealing with 32 and 64 bit systems. No matter what architecture you are building NSInteger will use the correct type.
    
     - You do not use *pointers when declaring NSIntegers
     - NSInteger allows math operations
     - When printing a NSInteger you'll need to use the %ld format specifier
     - Cannot be added to a collection. A conversion to NSNumber is necessary
     
Conversion Examples
    NSNumber -> NSInteger

NSInteger myInteger = [myNumber integerValue];

    NSInteger -> NSNumber 

NSNumber *myNumber = [NSNumber numberWithInteger:myInteger];

Now that it is all clear we won't be seeing any more warnings about