Wednesday, February 10, 2016

Swift tips: "Formatting optionals in a String" and "Cleaner localization using Swift"

FORMATTING OPTIONALS IN SWIFT STRINGS

Q: How do you format a String with optional (possibly nil) values but in a concise way?

A: Use  \(variable ?? "")

Let's say you have the following Person class.

Note:  This is just an example.  In real code,  you might use CNContact and it's built in formatting functions (that handle localization) but that's not the point of this tip.  It's to show you how to handle any case.

A person has a required first and last name and an optional middle name:

class Person {
  var firstName:String!
  var middleName:String?
  var lastName:String!

  func fullName() -> String {
    return "\(firstName) \(middleName) \(lastName)"
  }

}

APP:

var p = Person()
p.firstName = "Charlotte"
p.middleName = "Ann"
p.lastName = "Smith"

print(p.fullName())

OUTPUT:

Charlotte Optional("Ann") Smith


That's not what we wanted!

So let's say we  force unwrap the optional middleName using !

  func fullName() -> String {
    return "\(firstName) \(middleName!) \(lastName)"
  }

OUTPUT:

Charlotte Ann Smith


The problem is that if her middle name was "nil", the app would crash!


SOLUTION:

  func fullName() -> String {
    return "\(firstName ?? "") \(middleName ?? "") \(lastName ?? "")"
  }

The ?? operator checks if the variable not nil, and uses it.  If it it's nil, it uses the empty string "".



CLEANER LOCALIZATION USING SWIFT

Any developer who has localized/internationalized (i8n) their iOS app will tell you that their code is harder to enter and read because of the localization function names.

e.g.

NSLocalizedString("SUBMIT_BUTTON_TITLE", comment:"The title for the order form submit button.")

Many times, the comment isn't needed but in some cases it can help the translator decided between different contexts/connotations.  Unfortunately, you still have to supply that second argument when it's not needed.

How about if it was just:

i8n("SUBMIT_BUTTON_TITLE", "The title for the order form submit button.") 

or 

i8n("SUBMIT_BUTTON_TITLE")

You can simply your source code by adding these utility functions to your app.  It is NOT a class.  They are global functions.  Just put them in a .swift file by themselves or with other global functions.  e.g.  GlobalFunctions.swift

import Foundation

public func i8n(str:String) -> String {
    return NSLocalizedString(str, comment: "")
}

public func i8n(str:String,_ comment:String) -> String {
    return NSLocalizedString(str, comment: comment)
}



Wednesday, July 22, 2015

iOS: A simple example of a UITableView with UISearchController

One of my observations as a software developer is that there is a lack of essential coding examples.  By essential, I literally mean a thing that is absolutely necessary or it's "essence".  


  es·sence
  ˈesəns/

  noun

  noun: essence


  the intrinsic nature or indispensable quality of something, especially something abstract, that determines its character.

Examples should not be overly complex.  They exist to inform and educate.  Many programming examples on the Internet are too complicated when they should be simple.  I'll try my best to do this when possible.

So here is my first coding example for iOS in Objective C:

This example displays a list of fruit in a UITableView.  When the user pulls down on the list, the search bar appears.  When the user types in the search bar, the list is filtered to show only fruit names that match what they typed.  If the user clears or cancels the search, the full list is then displayed.  The results display inline in that they reuse the same UITableView.





In XCode, create a simple single view iOS application project.  Add a UITableView instance to the view, change the class of the view controller to ViewController, and make connections from the controller's tableView outlet to the UITableView and connections from the UITableView's delegate and dataSource back to the view controller.





ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UISearchControllerDelegate, UISearchBarDelegate, UISearchResultsUpdating>

@property (nonatomic, weak) IBOutlet UITableView * tableView;
@property (nonatomic, strong) UISearchController * searchController;
@property (nonatomic, strong) NSMutableArray * allItems;
@property (nonatomic, strong) NSMutableArray * filteredItems;
@property (nonatomic, weak) NSArray * displayedItems;

@end


ViewController.m


#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize tableView;
@synthesize searchController;
@synthesize allItems;
@synthesize displayedItems;
@synthesize filteredItems;

- (void)viewDidLoad {
    [super viewDidLoad];

    // Create a list of fruit to display in the table view.
    
    self.allItems = [[NSMutableArray alloc] init];
    [self.allItems addObject:@"Apples"];
    [self.allItems addObject:@"Oranges"];
    [self.allItems addObject:@"Pears"];
    [self.allItems addObject:@"Grapes"];
    [self.allItems addObject:@"Grapefruits"];
    [self.allItems addObject:@"Lemons"];
    [self.allItems addObject:@"Peaches"];
    [self.allItems addObject:@"Pineapples"];
    [self.allItems addObject:@"Cherries"];
    [self.allItems addObject:@"Bananas"];
    [self.allItems addObject:@"Watermelons"];
    [self.allItems addObject:@"Cantaloupes"];
    [self.allItems addObject:@"Limes"];
    [self.allItems addObject:@"Strawberries"];
    [self.allItems addObject:@"Blueberries"];
    [self.allItems addObject:@"Raspberries"];
    
    // Create a list to hold search results (filtered list)
    self.filteredItems = [[NSMutableArray alloc] init];
    
    // Initially display the full list.  This variable will toggle between the full and the filtered lists.
    self.displayedItems = self.allItems;
    
    // Here's where we create our UISearchController
    
    self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
    self.searchController.searchResultsUpdater = self;
    self.searchController.searchBar.delegate = self;
    
    [self.searchController.searchBar sizeToFit];
    
    // Add the UISearchBar to the top header of the table view
    self.tableView.tableHeaderView = self.searchController.searchBar;
    
    // Hides search bar initially.  When the user pulls down on the list, the search bar is revealed.
    [self.tableView setContentOffset:CGPointMake(0, self.searchController.searchBar.frame.size.height)];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
    return [self.displayedItems count];
}

- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)anIndexPath {
    
    UITableViewCell * cell = [aTableView dequeueReusableCellWithIdentifier:@"FruitCell"];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] init];
    }
    cell.textLabel.text = [self.displayedItems objectAtIndex:anIndexPath.row];
    return cell;
}

// When the user types in the search bar, this method gets called.
- (void)updateSearchResultsForSearchController:(UISearchController *)aSearchController {
    NSLog(@"updateSearchResultsForSearchController");
    
    NSString *searchString = aSearchController.searchBar.text;
    NSLog(@"searchString=%@", searchString);
    
    // Check if the user cancelled or deleted the search term so we can display the full list instead.
    if (![searchString isEqualToString:@""]) {
        [self.filteredItems removeAllObjects];
        for (NSString *str in self.allItems) {
            if ([searchString isEqualToString:@""] || [str localizedCaseInsensitiveContainsString:searchString] == YES) {
                NSLog(@"str=%@", str);
                [self.filteredItems addObject:str];
            }
        }
        self.displayedItems = self.filteredItems;
    }
    else {
        self.displayedItems = self.allItems;
    }
    [self.tableView reloadData];
}


@end


I hope you found this fruitful.  :-)  Feel free to suggest improvements, ask questions or tell me I'm doing it all wrong!

See also:

UISearchController
https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UISearchController/index.html

Apple's TableSearch example
https://developer.apple.com/library/ios/samplecode/TableSearch_UISearchController/Introduction/Intro.html

Updating to the iOS 8 Search Controller
http://useyourloaf.com/blog/2015/02/16/updating-to-the-ios-8-search-controller.html

myblog = [[TheTechBar alloc] init];


@TheTechBar



WELCOME!

As your tech bartender, I intend to use this blog to muse on technology, offer tips, code snippets and vent gratuitously on my frustrations with software development.  Grab a seat, a drink and an open mind.

Robert La Ferla