Center view within UIScrollView on zoom

- (void)centerContent 
{
    CGFloat top = 0, left = 0;
    if (self.scrollView.contentSize.width < self.scrollView.bounds.size.width) {
        left = (self.scrollView.bounds.size.width-self.scrollView.contentSize.width) * 0.5f;
     }
    if (self.scrollView.contentSize.height < self.scrollView.bounds.size.height) {
        top = (self.scrollView.bounds.size.height-self.scrollView.contentSize.height) * 0.5f;
    }
    self.scrollView.contentInset = UIEdgeInsetsMake(top, left, top, left);
}

We call this method in UIScrollViewDelegate method:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    [self centerContent];    
}

Don’t forget to center content in your UIScrollView setup (most likely, it is in
– (void)viewDidLoad or in scroll view setter)!

contentInset and contentOffset properties of UIScrollView

Created and inserted into controller like any other view, UIScrollView needs two additional steps to complete its configuration:

  • set the contents size property, which specifies the size of the scrollable area
  • add view or views that are displayed by scroll view

Even if we haven’t added any views to our UIScrollView yet, running NSLog(@”%@”, scrollView.subviews) will show us two UIImageViews – these are vertical and horizontal scrolling bars.

UIImageViews in UIScrollView
UIImageViews in UIScrollView

Adding one or many subviews depends on our application design, but the scroll view needs to support zooming, the most common solution is to add a single subview that encompasses the entire contentSize and then add additional subviews to that view.
The contentInset property specify a buffer area around the content of the scroll view. We use it, when we need to add padding around the edges of scroll view content.

contentInset
contentInset

It makes content area larger without changing the size of the subview or subview’s content.
To avoid confusion, let’s mention contentOffset property, which is a CGPoint value that defines the top-left corner of the scroll view bounds. It defines the point in the content that is visible at the top left of the scroll view bounds. It is used often, when we need to scroll programmatically as response to user actions (scroll to top in applications with vertical representation of the data).

Core Data is not a relational database

Core Data, according to Core Data Programming Guide, is not a relational database. At the same time, both Core Data and relational database provide searchable and persistent data storage. So, what is the difference?
Relational databases:

  • maintain relations between rows, columns or tables is done by user, options beyond “read” and “write” are very few

Core Data – object graph manager:

  • user can connect object A and B and this connection is kept sync
  • changes in connection trigger notifications and updates
  • handle deletion of objects (cascade or nullify)
  • possible to use without any form of searching – once objects allocated and connected, we can hold onto one of them and walk through everything without needing a fetch request
  • work on objects in memory – can get slow on big amount of data
  • no threading support (need several NSManagedContext instead)

NSSortDescriptors with Collections Operations

Suppose we need to sort objects based on the length of the array (property named transactions).
We can use NSSortDescriptors like that:

NSSortDescriptor *desc = [NSSortDescriptor sortDescriptorWithKey:@"transactions.@count" ascending:YES]; 
NSArray *descriptors = @[desc];
return [self.items sortedArrayUsingDescriptors:descriptors];

The general case for it is  Collections Operations:

NSNumber *numberOfTransactions = [transactions valueForKeyPath:@"@count"];

 

 

Selectors

Selectors are internal representation of a method name in Objective-C. They are used to SELECT a method to execute for an object, it identifies the method.
Selectors let us treat a method as if it was independent entity, and separate the action from the object that needs to perform it. This is the basis of Target-Action design pattern and is an integral part of Objective-C dynamics typing system.
In Target-Action design pattern the object contains the necessary information to send a message to another object when an event occurs. For example, the button needs to notify the view controller that the button was clicked (touch event) and then the view controller can act upon this information. Button here has a target (to whom) and action method (what message should be sent).

Target-Action example
Target-Action example

Dynamic typing means that object can be sent a message that is not specified in its interface. This allows an object to capture a message and send it to a different object that can respond to this message appropriately. This is known as message forwarding or delegation.

- (void) objDisplay: (id) object
{
    [object displayInfo];
}

There are two ways to get the selector for a method name:

  • @selector() – convert a source-code method name to a selector
  • NSSelectorFromString() – convert a string to a selector

Both return a special data type SEL

Selector code example
Selector code example

Selectors can be executed on arbitrary object via performSelector: and related methods. When we are not sure if the target object defines the method, we should use respondsToSelector: check before trying to perform a selector.
Sample project with selectors and delegates is on Github.

Recursive base converter

We solve the problem recursively, when we break it into smaller and smaller subproblems, until the solution becomes obvious.
There are three rules of recursion:

  1. Define a base case
  2. Change a state and move towards base case
  3. Call itself

Let’s see how they can be fulfilled using the base converter:

- (NSString *)recursiveBace: (NSInteger)number ToBase: (NSInteger)base
{
    NSArray *convert = @[@"0", @"1", @"2", @"3", @"4", @"5", @"6", @"7", @"8", @"9", @"A", @"B", @"C", @"D", @"E", @"F"];
    NSMutableString *result = [[NSMutableString alloc]init];
    if (number < base) { //detect the base case
        [result appendString:convert[number]]; //then return a string from sequence
    } else {
        [result appendString:[self recursiveBace:number/base ToBase:base]]; //reduce problem size and change state
        [result appendString:convert[number%base]];
    }
    return result;
}

Project files in Finder and Xcode

Imagine we have a basket of apples. I track this apples using a piece of paper and a pencil.

As we can see, although I have a separate categories for green and red apples on paper, in reality all apples are stored together in one basket. A contents of a basket is still reflected on my list correctly. My list obeys certain rules:

  •  if I add an apple on paper, it appears in the basket, otherwise the tracking wouldn’t be of use
  •  if I remove an apple from the basket, the line in my list corresponding to it does not represent an existing object anymore

With this picture in mind, let’s have a look at Xcode project files and how they correspond to files on disk in the project folder

Project in Xcode
Project in Xcode

We see that groups in Xcode project navigator and folders in Finder may not be of exact match.

Project in Finder
Project in Finder

The file named BaseConverter.xcodeproj contains all information about how to build the project. We can open the project by double-click it.
There are some rules present:

  1. The Base converter group looks real, it exists as a folder on disk. If we add more files to this folder through Xcode, they most likely will appear at this folder in Finder as well
  2. The Supporting files group doesn’t show itself on disk, it is just a way to combine some items together inside Xcode project so that’ll be easier to work with them. Contents of this group is real, however, and we can find this files on disk.
  3. Similar story happens with Base Converter group. The group exists only inside Xcode project, but the files are present in Finder.

The groups are used to make a navigation in project more intuitive, but they do not alter file structure on the disk. We create groups, by choosing File -> New -> Group

Frameworks and Products groups are different cases – they don’t correspond to anything in the project folder, but they correspond to real things that the project needs to build and run.

Also, as new users, we may get in trouble by messing with project folder in Finder. The easiest way to avoid this is to perform all our interactions with the project through project window in Xcode.

Convert decimal to binary

Helps to understand how binary count works:

- (NSString *)divideByTwo: (NSInteger) decimalNumber
{
    NSMutableString *binary = [[NSMutableString alloc]init];
    if (binary == 0) {
        [binary appendString:@"%@"];
    }
    while (decimalNumber > 0) {
        NSInteger rem = decimalNumber % 2;
        [binary appendString:[NSString stringWithFormat:@"%d", rem]];
        decimalNumber = decimalNumber / 2;
    }
    return binary;
}

There is also Any base converter on Github, which is a general case of base 2 conversion. It converts any decimal number to any base from 2 to 16 and mimics a stack behavior.

NSNumber vs NSInteger

The notion of NSInteger and NSNumber may seem confusing at first glance, but in reality it is simple.
NSNumber is a subclass of NSValue – container for C or Objective-C data item. The purpose of NSValue class, and, hence, NSNumber class – to allow items of different data types (ints, floats, chars, pointers, structures, as well as object ids) to be added to collections, such as instances of NSArray, for example. As simple is that!

With that purpose in mind, it’s easy to memorize NSNumber features:
1. it stores and retrieve primitives, but it’s NOT primitive;
2. it can be displayed using %@ format specifier;
3. it is incredible useful when we need to stick a number into NSArray of NSDictionary.

NSInteger also has some features:
1. it is very much like traditional int in C, only it save us from dealing with int/long on 32/64 bit processor architecture;
2. it is a primitive.

We can convert them between each other:

NSInteger myInt = [numberValue integerValue]; //NSNumber to NSInteger
//NSInteger to NSNumber
NSNumber myNum = [NSNumber numberWithInteger: integerValue]; 
NSNumber myNum = @(integer);