13

When the user touches a UIButton it gets grayed-out a bit. What apple does to get such an effect? I need the same effect on my custom UIButton when it is highlighted.

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

Im using this code, but changing background color wont affect any subviews. I need them to be grayed out too.

denis_lor
  • 6,212
  • 4
  • 31
  • 55
t0a0
  • 668
  • 1
  • 9
  • 18
  • I'm looking for a solution as well, but I'm fairly sure Apple is doing something more sophisticated. Looks like its determining the mask of the image and darkening only those areas. Not quite sure how they do it. – David Dec 14 '14 at 00:03
  • I have simple solution please refer it :https://stackoverflow.com/a/48161904/6630644 – SPatel Jan 09 '18 at 05:32

7 Answers7

21

You can use a custom view like this for example:

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

Then you can use it instead of UIView and whenever you will click on it, it will change it's alpha value so it will look like a highlight.

denis_lor
  • 6,212
  • 4
  • 31
  • 55
5

I don't think there is an easy or definitive answer to that.

DISCLAIMER: All the code in this answer was written from the top of my head, so please excuse mistakes.

I would suggest doing something like this:

Proposal 1: decrease opacity for all the view's subviews, which would not affect color...

Objective C:

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

Swift:

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

Proposal 2 (not tested): try to cover all subviews by doing it kind of manually (drawback: you would have to set the colors back manually as well, which would be insane if it is not monochromatic):

Objective 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];
        }
    }
}

Swift:

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

This is really bad design and will probably cause a lot of problems, but it could potentially help you. The example above needs a lot of improvement, I just want to give you a hint. You would have to revert the colors in the touchesEnded: method. Maybe it helps you...

Proposal 3: would be to overlay your whole view with a transparent view (if it is rectangular)

Objective C:

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

Swift:

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

You would have to remove it when the user releases the finger of course.

Proposal 4 : Another option would be to create a bitmap of your current view and modify the pixels to your liking, which is quite a bit of work, so I will omit code here.

Apple probably does a mix of the last two. When a button is touched it will go over the pixels and overlay a gray pixel over every pixel that has an alpha component.

I hope I could help.

denis_lor
  • 6,212
  • 4
  • 31
  • 55
the_critic
  • 12,720
  • 19
  • 67
  • 115
4

I know this is an old question. But if someone is still searching for a generic touch highlighting solution, here is a little category I have created to address the problem.

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

Usage:

Just import the category in your view controller and add the following line in viewDidLoad.

buttonView.touchHighlightingStyle = MTHighlightingStyleHollowDarkOverlay;

Different Highlighting Styles:

Screenshot

Tayyab Akram
  • 366
  • 3
  • 12
  • Your category looks good, but actually it is applied for all UIViews. The methods regarding the touch events are replaced in all views of the application. – chipbk10 Jul 04 '17 at 14:48
  • Yes, this is done with method swizzling, so all views are affected. But it is not a big deal as these methods are only called when a touch occurs, so performance won't be effected. The basic idea behind its development was the conversion of existing views into highlighting ones. There could have been a subclass of UIView but it would impact existing code base a lot. – Tayyab Akram Jul 06 '17 at 07:39
  • Correct me if I am wrong. The method swizzling is created in load method. It will be called from the very beginning (not as you said "only called when a touch occurs). If you put the breakpoint in the method swizzling, you will see. About subclassing, you might be right. However, subclassing must be implemented for all UIControl (e.g., UILabel, UIButton, UICollectionView, etc.) – chipbk10 Jul 06 '17 at 08:50
  • You are right that method swizzling is created in load method. But these swizzled methods are only called when a touch occurs. After handling the highlighting state, original implementation is invoked. It works similar to a subclass with overridden methods. – Tayyab Akram Jul 06 '17 at 13:07
  • this is a quick way to add button like behaviour to any element, though it feels strange to do it for a whole viewcontroller, how would I have to go if I want to use it only inside a class that inherits from UILabel? – Edoardo Aug 24 '17 at 14:42
  • It works on UIViews not view controllers. So you can apply it on your label like this: myLabel.touchHighlightingStyle = MTHighlightingStyleHollowDarkOverlay; – Tayyab Akram Aug 26 '17 at 05:39
  • I am talking about doing it for a class like this: `@interface MyLabel : UILabel` importing UIView+TouchHighlighting.h inside the class declaration, (and not in the VC that uses that class) I tried and it does not work... – Edoardo Aug 26 '17 at 11:21
  • Could it be that the user interaction of your label is disabled? – Tayyab Akram Aug 26 '17 at 13:43
4

Swift 3:

override touchesBegan and touchesEnded callbacks in ur custom UIView class

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
}

or better use drop shadow instead of changing background

CardView code except touches event borrowed from https://github.com/aclissold/CardView

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

    }

UPDATE:

I also added touchesCancelled callback to prevent my UIView stuck in selected state.

 override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        layer.shadowOffset = CGSize(width: shadowOffsetWidth, height: shadowOffsetHeight);
    } 
ugur
  • 3,604
  • 3
  • 26
  • 57
0

Ended up with this code

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];
            }
        }
    }
t0a0
  • 668
  • 1
  • 9
  • 18
0

Idea:

  1. Add button in which you want to trigger touch area.

  2. Use Event: Touch Down, Touch Up Inside and Touch Up Outside

  3. In Touch Down set Highlight state value, other set Normal vale.

You can use storyboard IBAction to do, or Programmatically.

Sample code(Programmatically):

- (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;
}
franiis
  • 1,378
  • 1
  • 18
  • 33
liu jeff
  • 21
  • 4
-1
  1. Create a tap gesture to your UIView

    UITapGestureRecognizer* buttonViewTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(categoryButtonAction:)];
    
  2. Now add below definition to your code. It will some how similar effect the View.

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

This will create some how similar effect as button touch effect.

Yogi
  • 253
  • 3
  • 5