Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I create a button with a background color for tvOS while still showing focus?

Tags:

uibutton

tvos

All I want to do is add a background color to a button for all states. But I want to maintain the automatic focus shadow that you get "for free" when using a system button in the tvOS storyboard. So far I haven't been able to find a combination that allows this.

Alternatively, I would also be interested in a way to programmatically add the shadow when the button is focused, but short of subclassing the button (which I haven't yet tried), I don't know how to do that either.

like image 463
livingtech Avatar asked Sep 22 '15 15:09

livingtech


5 Answers

The Ultimate Solution with inspiration from SomaMan. Just subclass all your custom buttons and you Get this:

enter image description here

includes: on tap animation and release and drag away.

//
//  CustomFocusButton.swift
//

import UIKit

class CustomFocusButton: UIButton {


let focusedScaleFactor : CGFloat = 1.2
let focusedShadowRadius : CGFloat = 10
let focusedShadowOpacity : Float = 0.25
let shadowColor = UIColor.blackColor().CGColor
let shadowOffSetFocused = CGSizeMake(0, 27)
let animationDuration = 0.2

override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator)
{
    coordinator.addCoordinatedAnimations({
            if self.focused{
                UIView.animateWithDuration(self.animationDuration, animations:{ [weak self] () -> Void in

                        guard let weakSelf = self else {return}
                        weakSelf.transform = CGAffineTransformMakeScale(weakSelf.focusedScaleFactor, weakSelf.focusedScaleFactor)
                        weakSelf.clipsToBounds = false
                        weakSelf.layer.shadowOpacity = weakSelf.focusedShadowOpacity
                        weakSelf.layer.shadowRadius = weakSelf.focusedShadowRadius
                        weakSelf.layer.shadowColor = weakSelf.shadowColor
                        weakSelf.layer.shadowOffset = weakSelf.shadowOffSetFocused

                    },completion:{ [weak self] finished in

                        guard let weakSelf = self else {return}
                        if !finished{
                            weakSelf.transform = CGAffineTransformMakeScale(weakSelf.focusedScaleFactor, weakSelf.focusedScaleFactor)
                            weakSelf.clipsToBounds = false
                            weakSelf.layer.shadowOpacity = weakSelf.focusedShadowOpacity
                            weakSelf.layer.shadowRadius = weakSelf.focusedShadowRadius
                            weakSelf.layer.shadowColor = weakSelf.shadowColor
                            weakSelf.layer.shadowOffset = weakSelf.shadowOffSetFocused
                        }

                    })
            } else {
                UIView.animateWithDuration(self.animationDuration, animations:{  [weak self] () -> Void in
                    guard let weakSelf = self else {return}

                    weakSelf.clipsToBounds = true
                    weakSelf.transform = CGAffineTransformIdentity

                    }, completion: {[weak self] finished in
                        guard let weakSelf = self else {return}
                        if !finished{
                            weakSelf.clipsToBounds = true
                            weakSelf.transform = CGAffineTransformIdentity
                        }
                })
            }
        }, completion: nil)
}

override func pressesBegan(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {
    UIView.animateWithDuration(animationDuration, animations: { [weak self] () -> Void in

        guard let weakSelf = self else {return}
        weakSelf.transform = CGAffineTransformIdentity
        weakSelf.layer.shadowOffset = CGSizeMake(0, 10);

    })
}

override func pressesCancelled(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {

    if focused{
        UIView.animateWithDuration(animationDuration, animations: { [weak self] () -> Void in
            guard let weakSelf = self else {return}
            weakSelf.transform = CGAffineTransformMakeScale(weakSelf.focusedScaleFactor, weakSelf.focusedScaleFactor)
            weakSelf.layer.shadowOffset = weakSelf.shadowOffSetFocused
        })
    }

}

override func pressesEnded(presses: Set<UIPress>, withEvent event: UIPressesEvent?) {

    if focused{
        UIView.animateWithDuration(animationDuration, animations: {[weak self] () -> Void in

            guard let weakSelf = self else {return}
            weakSelf.transform = CGAffineTransformMakeScale(weakSelf.focusedScaleFactor, weakSelf.focusedScaleFactor)
            weakSelf.layer.shadowOffset = weakSelf.shadowOffSetFocused
        })
    }
}
}
like image 176
ImpactZero Avatar answered Nov 03 '22 13:11

ImpactZero


Swift 4 /tvOS11 and better:

Set the ButtonType in the Interface Builder button properties to "Plain".

Add this private extension to your class:

private extension UIImage {

    static func imageWithColor(color: UIColor, size: CGSize) -> UIImage? {
        let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
        UIGraphicsBeginImageContextWithOptions(size, false, 0)
        color.setFill()
        UIRectFill(rect)
        let image: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        return image
    }
}

Then in your connected IBOutlet set the background image for the focused state of the button:

@IBOutlet weak var myButton: UIButton! {
    didSet {
        let backgroundImageSelected = UIImage.imageWithColor(color: .red, size: myButton.bounds.size)
        myButton.setBackgroundImage(backgroundImageSelected, for: .focused)
    }
}
like image 21
Jochen Holzer Avatar answered Oct 13 '22 15:10

Jochen Holzer


You can add a shadow for your custom button like this:

- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
    context.nextFocusedView.layer.shadowOffset = CGSizeMake(0, 10);
    context.nextFocusedView.layer.shadowOpacity = 0.6;
    context.nextFocusedView.layer.shadowRadius = 15;
    context.nextFocusedView.layer.shadowColor = [UIColor blackColor].CGColor;
    context.previouslyFocusedView.layer.shadowOpacity = 0;
}
like image 8
Leszek Szary Avatar answered Nov 03 '22 15:11

Leszek Szary


Wasn't happy with a simple colour change, so I made a custom button subclass to look more like the default animation you get with system buttons -

class CustomButton: UIButton
{
    private var initialBackgroundColour: UIColor!


    required init?(coder aDecoder: NSCoder)
    {
        super.init(coder: aDecoder)

        initialBackgroundColour = backgroundColor
    }

    override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator)
    {
        coordinator.addCoordinatedAnimations(
        {
            if self.focused
            {
                self.backgroundColor = UIColor.whiteColor()

                UIView.animateWithDuration(0.2, animations:
                {
                    self.transform = CGAffineTransformMakeScale(1.1, 1.1)
                },
                completion: 
                {
                    finished in

                    UIView.animateWithDuration(0.2, animations:
                    {
                        self.transform = CGAffineTransformIdentity
                    },
                    completion: nil)
                })
            }
            else
            {
                self.backgroundColor = self.initialBackgroundColour
            }
        },
        completion: nil)
    }
}

Nothing too complicated, but gets the job done

like image 7
SomaMan Avatar answered Nov 03 '22 13:11

SomaMan


Override didUpdateFocusInContext method and check if next focus view is button, if yes then customize its UI, and to set it back to orignal state check context.previousFocusedView was that button, something like below

- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
    if (context.nextFocusedView == _button)
    {
        // set background color
    }
    else if (context.previousFocusedView == _button)
    {
        // set background color to background
    }
}
like image 6
Adnan Aftab Avatar answered Nov 03 '22 15:11

Adnan Aftab