Monday, June 20, 2016

awakeFromNib, layoutSubviews, dequeueReusableCellWithIdentifier and what happens in what order

If you have customized code in your UITableViewCell subclass like myself, you usually have some

If you have customized code in your UITableViewCell subclass like myself, you usually have some startup code for the cell, usually setting some constraints, or colors of text, default placeholder, etc. It generally doesn't matter whether this code goes in awakeFromNib or layoutSubviews, as long as what you do after dequeueReusableCellWithIdentifier doesn't depend on logic in either of these startup methods.

In the case where it does, you need to know which one happens in which order, in my case I'm using a custom UITextView that has placeholder text, via a nice blog post on how to do that:

https://grokswift.com/uitextview-placeholder/

The issue was that when opening a form to fill out for a new document the text is empty, so you need placeholder text. On top of this I have a label that needs to show (alpha = 1) when text from the user is inputted. When no text exists in the TextView the label needs to animate away and the Placeholder text needs to appear.

So you can see the possible logic branches I have, the issue was that at first launch of the cell before any text is set (if text already exists), my custom placeholder UITextView needs to get setup with constraints, placeholder text, placeholder text color, etc. But when the text already exists it needs to be set after the deque method is called, which then should disable the label and put the real text in the TextView (thus disabling the placeholder).

I got in a bind because I couldn't figure out which of these 3 methods run in which order, in this case, print out to console obviously shows you.

Here's the 3 important parts of code:

(called from the UITableViewController)

let cell = tableView.dequeueReusableCellWithIdentifier(notesTextViewCellId)

(inside the UITableViewCell subclass)

override func layoutSubviews() {
    super.layoutSubviews()
    print("layoutSubviews called")

    // do startup code here?
}

override func awakeFromNib() {
    super.awakeFromNib()
    print("awakeFromNib called")

    // or here?
}

or both?

In my case it was both.

Constraints needed to be in the awakeFromNib() and setting the placeholder logic needed to be in the layoutSubviews() as that happens AFTER the cell is dequed, where the awakeFromNib() happens before.

Here's the order when printed out to the console:

awakeFromNib called
notesCell dequeued
layoutSubviews called

Tuesday, March 1, 2016

Draw a red border around your UIView for debugging purposes

A lot of the times, you're dealing with UITextViews and UITextFields that have the same

A lot of the times, you're dealing with UITextViews and UITextFields that have the same white background and border as the superviews they sit on top of. It's helpful every now and then to make sure they're obeying the constraints you want them to have. In my case, I made a self-contained UITextView that has a placeholder functionality but the textView wasn't constraining to the size of its container view.

DOH!

image

Ahh... needed better constraints:

func applyConstraintsToTextView() {

    if let validTextView = self.placeholderTextView {
        let viewsDictionary = ["textView":validTextView]
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[textView]|", options: [], metrics: nil, views: viewsDictionary))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[textView]|", options: [], metrics: nil, views: viewsDictionary))
        setNeedsLayout()
    }
}

Now add this extension to UIView

extension UIView {

func drawRedBorder() {
    self.layer.borderColor = UIColor.redColor().CGColor
    self.layer.borderWidth = 1.0;
    }
}

simply call it on the view (any UIView subclass) and see where the borders are landing.

validTextView.drawRedBorder()

Much better...

image

Monday, February 15, 2016

Quickly add a simple UIActivityIndicator to your screen

Sometimes you need to quickly throw a progress indicator over your view while something processes

Sometimes you need to quickly throw a progress indicator over your view while something processes (hopefully in the background).

I've found this code to be useful for that situation. Apply to your needs.

UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithFrame:self.view.frame];
activityIndicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
activityIndicatorView.backgroundColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.5];
[self.view addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];

[self.presentingViewController dismissViewControllerAnimated:YES completion:^{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
    dispatch_async(queue, ^{
        // do your processing in the background here
        dispatch_sync(dispatch_get_main_queue(), ^{
            // Update UI
            [activityIndicatorView stopAnimating];
            [activityIndicatorView removeFromSuperview];
        });
    });
}];

Sunday, January 17, 2016

Finder shortcut to open a directory path

I find myself using this a lot after a co-worker showed me. Basically, if you come across a directory path you need to get to, like to drop files into a folder or delete file, whatever, instead of manually clicking each directory to get to the destination, you can copy and paste the path into finder via

   ⌘ + Shift + G



Wednesday, January 6, 2016

Modularize your UIImagePickerController usage in Swift

When you run across quick tutorials or answers on using UIImagePickerController the majority have you When you run across quick tutorials or answers on using UIImagePickerController the majority have you simply instantiating, containing, delegating and using it inside your UIViewController. As with most coding examples this is for demonstration purposes only, you should always, always abstract out functionality into reusable pieces in any application. The most obvious reason is that if one view controller needs the image picking functionalities, it's a safe bet that another will at some point. If not, well you'll probably run across another need for it in another application, much easier to strip out the code if it's self-containined in one class.
The history of this code has been in Objective-C and it's served me well in more than one app, here I'm converting and using it in Swift for the first time in an app I'm currently building.
To start, create a class, I like using Controller at the end of most classes or structs of this nature.
class PhotoPickerController : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate { }
You do need to inherit from NSObject, if you don't the compiler will tell you that you either need to mark the delegate methods for UIImagePickerControllerDelegate with the prefix @objc or inherit from NSObject.
Now setup your instance variables/properties (now the same thing in Swift remember)
var alertController: UIAlertController?
weak var buttonToPresentPopoverForiPad: UIButton?
weak var viewController: UIViewController?
lazy var pickerController = UIImagePickerController()
1) You'll need a UIAlertController (ActionSheet) to ask the user if they want to take a picture or load from the device/cameraroll.
2) You need to have a weak reference to the button that will start the photo picker workflow so that if an iPad is used, the popOver functionality will work properly, remember on iPhone it's a modal popup, on iPad it's a pop-over.
3) You need a weak reference to the UIViewController that will be presenting the workflow.
4) Finally you need the core of all of this, an instance of UIImagePickerController, and because of swift lazy instantiation awesomeness, you can do it in one simple line. Create it when you first call it.
Now comes the init for your custom container class. This will be the largest method and I suggest breaking it into smaller pieces, but for the sake of clarity,
init(buttonToPresentPopoverForiPad button: UIButton, viewControllerToPresent viewController: UIViewController) {

    super.init()

    self.alertController = UIAlertController(title: "Select a Photo", message: nil, preferredStyle: .ActionSheet)
    self.buttonToPresentPopoverForiPad = button
    self.viewController = viewController

    let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action) in
        self.alertController?.dismissViewControllerAnimated(false, completion: nil)
    }

    alertController?.addAction(cancelAction)

    let photoAlbumAction = UIAlertAction(title: "Photo Album", style: .Default) { (action) in
        self.selectPicture(.PhotoLibrary)
    }

    alertController?.addAction(photoAlbumAction)

    let cameraAction = UIAlertAction(title: "Take a photo", style: .Default) { (action) -> Void in
        self.selectPicture(.Camera)
    }

    alertController?.addAction(cameraAction)
    alertController?.modalPresentationStyle = .Popover
    if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
        let popOverPresenter = alertController?.popoverPresentationController
        popOverPresenter?.sourceView = self.buttonToPresentPopoverForiPad
        popOverPresenter?.sourceRect = self.viewController?.view.bounds as CGRect!
        popOverPresenter?.permittedArrowDirections = .Any
    }
} 
First, init the super which is NSObject like always, next setup your AlertController to be an ActionSheet with the proper actions, cancel, photo album and take a photo. Again the benefit of the newer UIAlertController API is that you can set the action results for each action inline with blocks, no more delegating to other methods.
For Cancel, just dismiss the alert all together, nothing to see here, just move on.
For Photo Album and Take a picture, you create a method that will open the appropriate modal workflow for each selection.
Finally finish up your AlertController for dealing with the iPad.
Now, here is the selectPicture helper method:
private func selectPicture(pickerType: UIImagePickerControllerSourceType) {
    if UIImagePickerController.isSourceTypeAvailable(pickerType){
        pickerController.delegate = self
        pickerController.sourceType = pickerType;
        pickerController.mediaTypes = [kUTTypeImage as String]
        pickerController.allowsEditing = false
        pickerController.navigationBar.tintColor = UIColor.whiteColor()
        self.viewController?.presentViewController(pickerController, animated: true, completion: nil)
    }
}
I generally now make my methods private that I obviously don't want available in the classes API. That's just my personal preference.
Follow the standard practices now for setting up the UIImagePickerController outlined in Apple's documentation. Choose what will suit you for colors of the navbar, the mediatTypes, etc.
Notice we set the delegate of the UIImagePickerControllerDelegate to self, this container class will handle the callbacks for the UIImagePickerController, instead of the more common example of a UIViewController bloated and dirty with all sorts of tasks assigned to it.
Here's then how you handle those callbacks:
Make sure you use the proper MARK: so indicating the delegate methods
// MARK: - UIImagePickerControllerDelegate methods

func imagePickerControllerDidCancel(picker: UIImagePickerController) {
    self.pickerController.dismissViewControllerAnimated(true, completion: nil)
}

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
    let mediaType = info[UIImagePickerControllerMediaType] as! NSString;
    var originalImage, editedImage, imageToUse: UIImage?

    if (CFStringCompare(mediaType as CFStringRef, kUTTypeImage, .CompareCaseInsensitive) == CFComparisonResult.CompareEqualTo) {
        editedImage = info[UIImagePickerControllerEditedImage] as? UIImage
        originalImage = info[UIImagePickerControllerOriginalImage] as? UIImage

        if editedImage != nil {
            imageToUse = editedImage
        } else {
            imageToUse = originalImage
        }

        if let validDelegate = delegate, let validImage = imageToUse {
              validDelegate.photoPicker(self, didSelectImage: validImage)
        }
        picker.dismissViewControllerAnimated(true, completion: nil)

    }
 }
imagePickerControllerDidCancel is pretty straightforward, the only other method that you need would be imagePickerControllerDidFinishPickingMediaInfo, here you handle the image handed back to the delegate. Following standard documentation, you determine the mediatype that was selected, remember it could be a movie file, not just a photo. In our case we just want images. Then decide do you care about the edited image, did you allow image editing in the setup of the controller? Here's where you deal with that. Finally cast the image you want to an optional UIImage. Notice I then call another delegate (of this container class), indicating the image is ready to go and is valid. Now, here's really the last piece of functionality needed.
The user of your container class needs a way to get the image when it's all said and done and ready to give to a viewController and View. So lets go back to the top of your class file and create a protocol that declares a delegate method for use with your new class.
protocol PhotoPickerDelegate: class {

    func photoPicker(picker: PhotoPickerController, didSelectImage image: UIImage)
}
Now all you need to do is simply make your UIViewController or whatever else is using your class abide by this protocol. You could also use NSNotifications as well for even looser coupling, but I think the delegate pattern works fine for this.
Lastly, you need to implement this one method for the UINavigationControllerDelegate
/** UINavigationControllerDelegate is required to ensure the UIImagePickerController has a light status bar instead of black */
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
    UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent
}

Mvvm on Mobile?

Here's my talk from Houston Tech Fest 2017. Recorded Talk: Slides: https://speakerdeck.com/markawil/mvvm-and-mobile-dont-do-it...