如何在仍显示焦点的同时为 tvOS 创建具有背景颜色的按钮?

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

我只想为所有状态的按钮添加背景色。但我想保留 "for free" 在 tvOS 故事板中使用系统按钮时获得的自动焦点阴影。到目前为止,我还没有找到允许这样做的组合。

或者,我也对一种在按钮获得焦点时以编程方式添加阴影的方法感兴趣,但是缺少子类化按钮(我还没有尝试过),我不知道该怎么做那个。

覆盖 didUpdateFocusInContext 方法并检查下一个焦点视图是否为按钮,如果是则自定义其 UI,并将其设置回原始状态检查 context.previousFocusedView 是否为该按钮,类似下面

- (void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator
{
    if (context.nextFocusedView == _button)
    {
        // set background color
    }
    else if (context.previousFocusedView == _button)
    {
        // set background color to background
    }
}

您可以像这样为自定义按钮添加阴影:

- (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;
}

我找到了更好的东西:

-(void)didUpdateFocusInContext:(UIFocusUpdateContext *)context withAnimationCoordinator:(UIFocusAnimationCoordinator *)coordinator {
    [coordinator addCoordinatedAnimations:^{
        if (self.focused) {
            self.backgroundColor = [UIColor whiteColor];
        }
        else {
            self.backgroundColor = [UIColor clearColor];
        }
    } completion:nil];
}

对简单的颜色更改不满意,所以我制作了一个自定义按钮子类,使其看起来更像您使用系统按钮获得的默认动画 -

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)
    }
}

没有什么太复杂,但可以完成工作

您可以使用 UIButton 方法 setBackgroundImage(image: UIImage?, forState state: UIControlState) 并通过一个图像,该图像是纯色和状态 .Normal

可以通过 UIColor 和大小为 1x1:

的程序轻松创建此图像
func getImageWithColor(color: UIColor, size: CGSize) -> UIImage {
    let rect = CGRectMake(0, 0, size.width, size.height)
    UIGraphicsBeginImageContextWithOptions(size, false, 0)
    color.setFill()
    UIRectFill(rect)
    let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

灵感来自 SomaMan 的终极解决方案。只需对所有自定义按钮进行子类化即可得到:

包括:点击动画和释放并拖走。

//
//  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
        })
    }
}
}

您可以将故事板中的背景图片设置为包含您想要的颜色的图片

Swift 4 /tvOS11 及更高版本:

将 Interface Builder 按钮属性中的 ButtonType 设置为 "Plain"。

将此私人分机添加到您的 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
    }
}

然后在您连接的 IBOutlet 中为按钮的聚焦状态设置背景图像:

@IBOutlet weak var myButton: UIButton! {
    didSet {
        let backgroundImageSelected = UIImage.imageWithColor(color: .red, size: myButton.bounds.size)
        myButton.setBackgroundImage(backgroundImageSelected, for: .focused)
    }
}