Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make UILabel focusable and tappable (tvOS)

I'm trying to implement 6 lines high description label and I want it to be focusable. Ideally that would mean extending UILabel class to make a custom component. I tried that by implementing canBecomeFocused and didUpdateFocusInContext but my UILabel doesn't seem to get any focus.

I also tried replacing UILabel with UIButton, but buttons aren't really optimised for this sort of thing. Also that would mean I'd need to change buttonType on focus from custom to plain.. and buttonType seems to be a ready-only property.

In reality I'd like to have exact same text label implemented by Apple in Apple TV Movies app. For movie description they have a text label that displays a few lines of text and a "more". When focused it looks like a button (shadows around) and changed background color. When tapped - it opens up a modal window with entire movie description.

enter image description here

Any suggestions? Or maybe someone has already implemented this custom control for tvOS? Or event better - there is a component from Apple that does this and I'm missing something.

P.S: Swift solution would be welcome :)

like image 342
kernelpanic Avatar asked Nov 23 '15 13:11

kernelpanic


2 Answers

Ok, answering my own question :)

So it appears that some some views are "focusable" on tvOS out-of-the-box, and other have to be instructed to do so.

I finally ended up using UITextView, which has a selectable property, but if not one of these focusable views by default. Editing of TextView has to be disabled to make it look like UILabel. Also, currently there is a bug which prevents you from using selectable property from Interface Builder but works from code.

Naturally, canBecomeFocused() and didUpdateFocusInContext had to be implemented too. You'll also need to pass a UIViewController because UITextView is not capable of presenting a modal view controller. Bellow is what I ended up creating.

class FocusableText: UITextView {

    var data: String?
    var parentView: UIViewController?

    override func awakeFromNib() {
        super.awakeFromNib()
        let tap = UITapGestureRecognizer(target: self, action: "tapped:")
        tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
        self.addGestureRecognizer(tap)
    }

    func tapped(gesture: UITapGestureRecognizer) {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        if let descriptionView = storyboard.instantiateViewControllerWithIdentifier("descriptionView") as? DescriptionViewController {
            if let view = parentView {
                if let show = show {
                    descriptionView.descriptionText = self.data
                    view.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
                    view.presentViewController(descriptionView, animated: true, completion: nil)
                }
            }
        }
    }

    override func canBecomeFocused() -> Bool {
        return true
    }

    override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {

        if context.nextFocusedView == self {
            coordinator.addCoordinatedAnimations({ () -> Void in
                self.layer.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor
            }, completion: nil)
        } else if context.previouslyFocusedView == self {
            coordinator.addCoordinatedAnimations({ () -> Void in
                self.layer.backgroundColor = UIColor.clearColor().CGColor
            }, completion: nil)
        }
    }

}
like image 121
kernelpanic Avatar answered Oct 06 '22 23:10

kernelpanic


As for making a UILabel focusable:

class MyLabel: UILabel {

    override var canBecomeFocused: Bool {
        return true
    }

    override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
        super.didUpdateFocus(in: context, with: coordinator)

        backgroundColor = context.nextFocusedView == self ? .blue:.red

    }
}

IMPORTANT!!! As stated on the apple developer portal:

The value of this property is true if the view can become focused; false otherwise.

By default, the value of this property is false. This property informs the focus engine if a view is capable of being focused. Sometimes even if a view returns true, a view may not be focusable for the following reasons:

  • The view is hidden.

  • The view has alpha set to 0.

  • The view has userInteractionEnabled set to false.

  • The view is not currently in the view hierarchy.

like image 4
Reimond Hill Avatar answered Oct 07 '22 00:10

Reimond Hill