如何像 Apple 一样突出显示 UIView

Posted

技术标签:

【中文标题】如何像 Apple 一样突出显示 UIView【英文标题】:How to highlight a UIView like Apple does 【发布时间】:2014-02-08 05:40:29 【问题描述】:

当用户触摸 UIButton 时,它会变灰一点。什么苹果能达到这样的效果?当我的自定义 UIButton 突出显示时,我需要它具有相同的效果。

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
    AppDelegate* appDelegate=(AppDelegate*)[UIApplication sharedApplication].delegate;
    if ([keyPath isEqualToString:@"highlighted"])
        UIButton *button = object;
        if (button.isHighlighted) 
            self.backgroundColor=[UIColor colorWithRed:36.0/255.0 green:153.0/255.0 blue:116.0/255.0 alpha:1];
        else
            self.backgroundColor=appDelegate.currentAppColor;
        
    

我正在使用此代码,但更改背景颜色不会影响任何子视图。我也需要它们变灰。

【问题讨论】:

我也在寻找解决方案,但我相当肯定 Apple 正在做一些更复杂的事情。看起来它确定了图像的蒙版并仅使那些区域变暗。不太清楚他们是怎么做到的。 我有简单的解决方案,请参考:***.com/a/48161904/6630644 【参考方案1】:

我认为对此没有简单或明确的答案。

免责声明: 此答案中的所有代码都是从我的脑海中编写的,所以请原谅错误。

我建议这样做:

建议一:降低所有视图子视图的不透明度,这不会影响颜色...

目标 C:

-(void)buttonTouched
    for(UIView *subview in self.subviews) 
        subview.alpha = 0.5;
    

斯威夫特:

func buttonTouched() 
    for subview in subviews 
        subview.alpha = 0.5
    

建议 2(未测试):尝试通过手动操作来覆盖所有子视图(缺点:您还必须手动设置颜色,如果是这样,那将是疯狂的不是单色的):

目标 C:

-(void)buttonTouched
    for(UIView *subview in self.subviews)

        if([subview respondsToSelector:@selector(setBackgroundColor:)]) 
            //for generic views - changes UILabel's backgroundColor too, though
            subview.backgroundColor = [UIColor grayColor];
        

        if([subview respondsToSelector:@selector(setTextColor:)]) 
            //reverse effect of upper if statement(if needed)
            subview.backgroundColor = [UIColor clearColor];
            subview.textColor = [UIColor grayColor];
        
    

斯威夫特:

func buttonTouched() 
    for subview in subviews 
        if subview.responds(to: #selector(setter: AVMutableVideoCompositionInstruction.backgroundColor)) 
            //for generic views - changes UILabel's backgroundColor too, though
            subview.backgroundColor = UIColor.gray
        

        if subview.responds(to: #selector(setter: UILabel.textColor)) 
            //reverse effect of upper if statement(if needed)
            subview.backgroundColor = UIColor.clear
            subview.textColor = UIColor.gray
        
    

这是一个非常糟糕的设计,可能会导致很多问题,但它可能会对您有所帮助。上面的例子需要很多改进,我只是想给你一个提示。您必须恢复 touchesEnded: 方法中的颜色。也许对你有帮助……

建议 3:用透明视图(如果是矩形)覆盖整个视图

目标 C:

-(void)buttonTouched
    UIView *overlay = [[UIView alloc]initWithFrame:self.bounds];
    overlay.backgroundColor = [[UIColor grayColor]colorWithAlphaComponent:0.5];
    [self addSubview: overlay];

斯威夫特:

func buttonTouched() 
    let overlay = UIView(frame: bounds)
    overlay.backgroundColor = UIColor.gray.withAlphaComponent(0.5)
    addSubview(overlay)

当然,当用户松开手指时,您必须将其移除。

建议 4:另一种选择是创建当前视图的位图并根据自己的喜好修改像素,这是一项相当多的工作,因此我将在此处省略代码。

Apple 可能会混合使用后两者。当一个按钮被触摸时,它将越过像素并在每个具有 alpha 分量的像素上覆盖一个灰色像素。

我希望我能帮上忙。

【讨论】:

我想到了你的方法3,最后还是用了它。 很高兴我能帮上忙 :-)【参考方案2】:

结束了这段代码

maskView=[[UIView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width, frame.size.height)];
maskView.backgroundColor=[[UIColor blackColor]colorWithAlphaComponent:0.5];

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
        if ([keyPath isEqualToString:@"highlighted"])
            UIButton *button = object;
            if (button.isHighlighted) 
                [self addSubview:maskView];
            else
                [maskView removeFromSuperview];
            
        
    

【讨论】:

【参考方案3】:

    为你的 UIView 创建一个点击手势

    UITapGestureRecognizer* buttonViewTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(categoryButtonAction:)];
    

    现在将以下定义添加到您的代码中。它会在视图中产生一些相似的效果。

    -(void)touchGestureAction:(UIGestureRecognizer*)gesture
    
        UIView * sender =  gesture.view;
    
        UIColor *color = sender.backgroundColor;
        sender.backgroundColor = [UIColor whiteColor];
        sender.alpha = 0.7;
    
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, 0.1 * NSEC_PER_SEC);
    //  This will create flash effect
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void)
            sender.backgroundColor = color;
            sender.alpha = 1.0;
        );
    
    

这将创建一些与按钮触摸效果非常相似的效果。

【讨论】:

【参考方案4】:

我知道这是一个老问题。但是,如果有人仍在寻找通用的触摸高亮解决方案,我创建了一个小类别来解决这个问题。

https://github.com/mta452/UIView-TouchHighlighting

用法:

只需在视图控制器中导入类别并在 viewDidLoad 中添加以下行。

buttonView.touchHighlightingStyle = MTHighlightingStyleHollowDarkOverlay;

不同的突出显示样式:

【讨论】:

您的分类看起来不错,但实际上它适用于所有 UIView。在应用程序的所有视图中都替换了有关触摸事件的方法。 是的,这是通过方法调配完成的,因此所有视图都会受到影响。但这没什么大不了的,因为这些方法仅在触摸发生时才调用,因此不会影响性能。其发展背后的基本思想是将现有观点转换为突出观点。可能有一个 UIView 的子类,但它会对现有的代码库产生很大影响。 如果我错了,请纠正我。方法 swizzling 是在 load 方法中创建的。它会从一开始就被调用(不是你说的“只有在触摸发生时才调用)。如果你将断点放在方法swizzling中,你会看到。关于子类化,你可能是对的。但是,必须实现子类化对于所有 UIControl(例如,UILabel、UIButton、UICollectionView 等) 你说得对,方法调配是在加载方法中创建的。但是这些 swizzled 方法仅在发生触摸时才被调用。处理高亮状态后,调用原始实现。它的工作方式类似于具有重写方法的子类。 这是一种向任何元素添加类似按钮的行为的快速方法,尽管为整个视图控制器执行此操作感觉很奇怪,如果我只想在类中使用它,我该怎么办继承自 UILabel?【参考方案5】:

斯威夫特 3:

在您的自定义 UIView 类中覆盖 touchesBegantouchesEnded 回调

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
    self.layer.backgroundColor = UIColor.white.cgColor


override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
    self.layer.backgroundColor = UIColor.gray.cgColor

或者更好地使用阴影而不是改变背景

CardView 代码,除了从https://github.com/aclissold/CardView 借来的 touches 事件

@IBDesignable
class CardView: UIView 

    @IBInspectable var cornerRadius: CGFloat = 5

    @IBInspectable var shadowOffsetWidth: Int = 0
    @IBInspectable var shadowOffsetHeight: Int = 1
    @IBInspectable var shadowColor: UIColor? = UIColor.black
    @IBInspectable var shadowOpacity: Float = 0.23

    override func layoutSubviews() 
        layer.cornerRadius = cornerRadius
        let shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)

        layer.masksToBounds = false
        layer.shadowColor = shadowColor?.cgColor
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
        layer.shadowOpacity = shadowOpacity
        layer.shadowPath = shadowPath.cgPath
    

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight * 2);

    

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: 

    shadowOffsetHeight);
        

    

更新:

我还添加了touchesCancelled 回调以防止我的UIView 卡在选定状态。

 override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) 
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
     

【讨论】:

【参考方案6】:

想法:

    添加要触发触摸区域的按钮。

    使用事件:Touch Down、Touch Up Inside 和 Touch Up Outside

    在Touch Down设置Highlight状态值,其他设置Normal vale。

您可以使用情节提要 IBAction 来做,或以编程方式。

示例代码(以编程方式):

- (void)viewDidLoad 
    [super viewDidLoad];

    firstView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
    firstView.backgroundColor = [UIColor blueColor];

    firstBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
    [firstBtn addTarget:self action:@selector(touchDownCollection) forControlEvents:UIControlEventTouchDown];
    [firstBtn addTarget:self action:@selector(touchUpInSideCollection) forControlEvents:UIControlEventTouchUpInside];
    [firstBtn addTarget:self action:@selector(touchUpOutSideCollection) forControlEvents:UIControlEventTouchUpOutside];

    [self.view addSubview:firstView];
    [self.view addSubview:firstBtn];

    //Need add: firstView.userInteractionEnabled = NO; if
    //[self.view addSubview:firstBtn];
    //[self.view addSubview:firstView];


- (void)touchDownCollection 
    // Highlight state value: alpha, background...
    firstView.alpha = 0.5;


- (void)touchUpInSideCollection 
    // Normal state value.
    firstView.alpha = 1.0;


- (void)touchUpOutSideCollection 
    // Normal state value.
    firstView.alpha = 1.0;

【讨论】:

【参考方案7】:

您可以使用自定义视图,例如:

class HighlightView: UIView 

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        DispatchQueue.main.async 
            self.alpha = 1.0
            UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveLinear, animations: 
                self.alpha = 0.5
            , completion: nil)
        
    

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) 
        DispatchQueue.main.async 
            self.alpha = 0.5
            UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveLinear, animations: 
                self.alpha = 1.0
            , completion: nil)
        
    

    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) 
        DispatchQueue.main.async 
            self.alpha = 0.5
            UIView.animate(withDuration: 0.4, delay: 0.0, options: .curveLinear, animations: 
                self.alpha = 1.0
            , completion: nil)
        
    

然后你可以使用它来代替UIView,当你点击它时,它会改变它的alpha值,所以它看起来像一个亮点。

【讨论】:

记得在重写这些方法时调用超级实现,如documentation 所述。

以上是关于如何像 Apple 一样突出显示 UIView的主要内容,如果未能解决你的问题,请参考以下文章

UIView 中的“BOOL 突出显示”属性而不是 UITableViewCell?

WinForms TreeView - 如何手动“突出显示”节点(就像点击它一样)

使视图变暗,就像禁用一样

如何突出显示带有孩子的 div(带有部分不透明层?)就像雅虎邮件一样,参见图片

长按时像 iMessage 一样突出显示 UITableViewCell

如何像 Apple Pro Apps 一样自定义 NSMenu?