Monday, April 10, 2017

Changing the line spacing only for the last line in a label with attributed text

Lately I've been working on a code-only UICollectionView, and I've been experimenting a little bit with the self-sizing UICollectionViewCells. Needless to say, nightmares followed. Having been involved in Android development previously, it was never clear to me why UILabels behave the way they do – the default behavior being truncating – and not automatically create new lines without what's called Auto Layout.

I wanted to create a collection view, which would list the notifications in an app. I was after something that looked like below:


The notification text could be arbitrarily long, with the time indicator being a little bit lower at the bottom. Now, every single developer that has a little bit of experience with auto layout knows that working with multiple labels is a one-way ticket to programmer's hell. So using a separate label for the time was a no-no. Thankfully, there's also NSAttributedString. With this type of object, it is possible to create a string which is composed of multiple substrings, each with their own custom styling. So I could create the text above using something like below:

notificationAttrText = NSMutableAttributedString(string: notificationText, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 14)])

timeAttrText = NSMutableAttributedString(string: "\n\(timeText)", attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 11), NSForegroundColorAttributeName: UIColor.init(colorLiteralRed: 158/255, green: 158/255, blue: 158/255, alpha: 1)])

let combined = NSMutableAttributedString()
        
combined.append(_nameAttrText)
combined.append(_verbAttrText)
notificationLabel.attributedText = combined

This was enough to create my label to look the way I wanted it, but... The time text was pretty close to the notification text.
At first I thought this is a pretty easy one. There is a way to add paragraph styling to an NSMutableAttributedString, by specifying the NSMutableParagraphStyle which you create, and also the range of the text you want to apply this attribute on. The drawback of this method is that you need to know the exact length of the line above. This is because you need two lines to define the spacing between them , so the range must be from the beginning of the line above, to the ending of the line below. In my case I needed to know the range for this piece of text "spacing only for the last line in a NSAttributedText\n2 hours ago". Obviously, it's not an easy thing to do. There's no easy way to get a specific line in a label. So I was doomed...

Chapter II - Redemption

Except I wasn't. NSMutableParagraphStyle to the rescue, everyone. But how, I hear you asking. Well, the answer lies on another property of this type of object. Not lineSpacing, but lineHeightMultiple. By using this property, I only needed to know the range for the time text, which I could very easily determine.

let paragraph = NSMutableParagraphStyle()
paragraph.lineHeightMultiple = 1.2
            
let length = _timeAttrText.length
let start = combined.length - length
            
combined.addAttribute(NSParagraphStyleAttributeName, value: paragraph, range: NSMakeRange(start, length))

The actual line height is multiplied by the value you assign to the lineHeightMultiple attribute, so a value of 1.2 would actually raise the height of the line by 20%, giving me the result I wanted.

Wednesday, April 5, 2017

Fixing "AppName.app is damaged and can’t be opened" in macOS

Most of the suggestions (all, actually) that I've seen about fixing the "AppName.app is damaged and can't be opened" error that pops up in macOS when opening a downloaded app, go through the route of disabling GateKeeper through the following command:

sudo spctl --master-disable

The problem is that most of the times, you can fix this issue without doing dangerous things; disabling GateKeeper is never a solution, it's simply an ugly workaround.

Anyways, this issue can sometimes be fixed with another command. Normally OSX (macOS) keeps track of file metadata, such as downloaded from. The metadata can be accessed through the xattr command line program.

I ran xattr on my downloaded app:

Eltons-MacBook-Pro:Applications elton$ xattr AppName.app/
com.apple.quarantine

So I figured I could delete the attribute:

Eltons-MacBook-Pro:Applications elton$ xattr -d com.apple.quarantine AppName.app/

Next, I tried to open the app, and bam! The app loaded without a warning. So that's it!

Tuesday, February 7, 2017

Use constants for your dynamic UITableView sections

I started this blog as a way to post random thoughts on reverse-engineering and related stuff. However, I also do iOS development (Android development too, for that matter). Hence, this...short blog post.

Let's suppose we are writing a fictitious app about displaying pictures of dogs and cats, separated in two sections: one for the dogs, and the other one for the cats. A common pattern seen in code written in tutorials all over the web — and books...sigh — when creating dynamic table views, is having code similar to that shown below:

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0 {
        return dogs.count
    } else if section == 1 {
        return cats.count
    } else {
        return 0
    }
}


This is only one method, which we use to determine how many rows each section will have. Now, we've decided that it would be cool if we displayed pictures of dogs in a custom UITableViewCell, and pictures of cats in another custom UITableViewCell:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if indexPath.section == 0 {
        let cell = tableView.dequeueReusableCell(withIdentifier: "doggoCell", for: indexPath) as! DogCell
        cell.dogImage = dogs[indexPath.row].image
        return cell
    } else { // indexPath.section == 1
        let cell = tableView.dequeueReusableCell(withIdentifier: "catCell", for: indexPath) as! CatCell
        cell.catImage = cats[indexPath.row].image
        return cell
    }
}

Alright, looking good. Now we decide that it's time to provide a custom title for each section:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == 0 {
        return "No bad boys allowed"
    } else if section == 1 {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

We deserve a pat on the back for the hard work, except that we need to do, ahem...one last thing. Somehow we decide that it would be a great idea to include a special section for owls. Don't get me wrong though, not just any type of owl. Superb owls only. As if that made any difference. But I have even better news: market research has shown that our app would have much greater success if we placed cats first, followed by owls, and lastly dogs (sorry dogs, nothing personal).

I hope that by now you understand that we've crafted our failure with our own hands. Hardcoding sections (and rows for that matter) in the various table view delegate and data source methods is woefully wrong. The solution to this problem is simple: declare constants for each section (or for rows, similarly). Doing this would make it easier for us in the future should the need arise to add some kind of other animal in our app:

let sectionDogs = 0
let sectionCats = 1

In such a case, our original titleForHeaderInSection method would look like this:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    }
    
    return nil
}

Not only would it be easier to the eye to understand which section you are setting up, but also the late minute changes described above (adding the owls) would be a breeze. Let me illustrate this only for the titleForHeaderInSection method:

override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    if section == sectionDogs {
        return "No bad boys allowed"
    } else if section == sectionCats {
        return "We can haz cheezburgerz"
    } else if section == sectionOwls {
        return "Don't call me, owl call you!"
    }
    
    return nil
}

And we would only need to add the sectionOwls constant, and also adjust the sectionDogs and sectionCats constants:

let sectionDogs = 2
let sectionCats = 0
let sectionOwls = 1

So by merely changing the values of each of the constants, we could easily reorder sections, without touching the data source and delegate methods.

I hope this was an interesting read, and I really hope that this will prevent any (further?) headache to future developers who are just starting out.