Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS increase touch area for UIButton in TableViewCell

I have a UIButton in my UITableViewCell

I made a subclass of UIButton override the pointInside function:

var touchMargin:CGFloat = 20.0

override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
    let extendedArea = CGRectInset(self.bounds, -touchMargin, -touchMargin)
    return CGRectContainsPoint(extendedArea, point)
}

However, the touch area does not get increased.
I get a touch on the table cell if I touch slightly outside of the UIButton.

Does this code not work due to the Button being placed in a cell?
How can I fix?

like image 644
Lord Vermillion Avatar asked Oct 13 '15 11:10

Lord Vermillion


3 Answers

Swift 3

//
//  ViewController.swift
//  test
//
//  Created by David Seek on 9/29/16.
//  Copyright © 2016 David Seek. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let button = MyButton(frame: CGRect(x: 200, y: 200, width: 100, height: 100))
        button.backgroundColor = UIColor.white
        button.addedTouchArea = 50 // any value you want
        button.addTarget(self, action:#selector(self.action), for: .touchUpInside)
        self.view.addSubview(button)
    }

    func action() {
        print("touched")
    }

}

class MyButton: UIButton {
    var addedTouchArea = CGFloat(0)

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {

        let newBound = CGRect(
            x: self.bounds.origin.x - addedTouchArea,
            y: self.bounds.origin.y - addedTouchArea,
            width: self.bounds.width + 2 * addedTouchArea,
            height: self.bounds.width + 2 * addedTouchArea
        )
        return newBound.contains(point)
    }
}

You are creating a UIButton in the size of 100x100, and with .addedTouchArea, you have a UIButton - still optical size 100x100, but with a touch area of 150x150.

Swift 2.X

class MyButton: UIButton {
    var addedTouchArea = CGFloat(0)

    override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {

        let newBound = CGRect(
            x: self.bounds.origin.x - addedTouchArea,
            y: self.bounds.origin.y - addedTouchArea,
            width: self.bounds.width + 2 * addedTouchArea,
            height: self.bounds.width + 2 * addedTouchArea
        )
        return newBound.contains(point)
    }
}

InterfaceBuilder

If you did set the Button with the InterfaceBuilder, apply our subclass of UIButton to your button.

enter image description here

Then set an outlet to the button, f.e. named buttonXY. And set buttonXY.addedTouchArea = 50 within viewDidLoad, f.e.

UITableViewCell

Since you're asking for UITableViewCell. It works exactly the same.

In your ViewController class:

extension ViewController: UITableViewDelegate, UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell") as! TableViewCell
        cell.selectionStyle = UITableViewCellSelectionStyle.none

        cell.myButton.addedTouchArea = 50 // any value you want
        tableView.rowHeight = 200

        return cell
    }
}

In your Cell class:

class TableViewCell: UITableViewCell {

    @IBOutlet weak var myButton: MyButton!

    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

    @IBAction func myButtonAction(_ sender: AnyObject) {
        print("touched")
    }
}

The only thing you should beware of: The outlet got set as a UIButton outlet, even tho I had declared it as subclass of MyButton within the InterfaceBuilder. I had to change the outlet manually to MyButton! @IBOutlet weak var myButton: MyButton!

like image 105
David Seek Avatar answered Nov 14 '22 06:11

David Seek


you should override hitTest on the view that you want to enlarge.

e.g.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    CGRect touchRect = CGRectInset(self.bounds, -10, -10);
    if (CGRectContainsPoint(touchRect, point)) {
        //TODO!? check supviews
        return self;
    }
    return nil;
}
like image 44
Daij-Djan Avatar answered Nov 14 '22 06:11

Daij-Djan


First you need to create UIButton subclass:

class ButtonWithTouchSize: UIButton {

    var touchAreaPadding: UIEdgeInsets? = nil

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        if let inset = touchAreaPadding {
            let origin = CGPoint(x: 0 - inset.left, y: 0 - inset.top)
            let size = CGSize(width: inset.left + bounds.width + inset.right,
                              height: inset.top + bounds.height + inset.bottom)
            let rect = CGRect(origin: origin, size: size)
            return rect.contains(point)
        } else { 
            return super.point(inside: point, with: event)
        }
    }
}

If you put an instance of ButtonWithTouchSize directly into UITableViewCell contentView, you'll get it. But if you wrap this instance in some container wich is in UITableViewCell contentView, your should also override its point(inside:with) method:

class ContainerView: UIView {

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        return btn.point(inside: point, with: event)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        btn.frame = self.bounds
    }

    private let btn: ButtonWithTouchSize = ButtonWithTouchSize()

}

So, to sum up... you should override point(inside:with) for each superview, which size is lessThanOrEqual to button size

like image 1
user2637348 Avatar answered Nov 14 '22 06:11

user2637348