Subview Gesture Recognizer not being called - ios

I am having a problem getting the UITapGestureRecognizer in my custom UIView.to work properly. I Have created a view: CategoryViewButton which adds a UITapGestureRecognizer in the init:
class CategoryViewButton: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.handleTap))
self.addGestureRecognizer(tapGesture)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func handleTap() {
print("Hello again")
}
}
This gesture recognizer works without issue when added directly in a View Controller. However, when I add a CategoryViewButton as a subview of another custom view, the gesture recognizer method does not get called. My subview:
class CategoryView: UIView, CategoryButtonDelegate {
var button : CategoryViewButton?
override init(frame: CGRect) {
super.init(frame: frame)
button = CategoryViewButton(frame: CGRect(x: 10, y: 0, width: 40, height: 25))
self.addSubview(button!)
self.bringSubview(toFront: button!)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
When I create a CategoryView in a View Controller, the handleTap() function is not being called. What am I missing?

For anyone curious, the issue was that the subview with gesture recognizer was outside the frame of the superview. This means even though the view was being drawn, the gestures were not detected

Related

UIBarButtonItem's customView accessibility

I've created UIBarButtonItem with customView set to BadgeButton (a UIButton that has badge label added).
I am setting accessibilityIdentifier = "properIDHere" and isAccessibilityElement = true for both: aBadgeButton and aBadgeButton.badgeLabel
When running UITests the badge button is visible and accessible by the ID, but the badgeLabel is not. Do you have ideas why?
final class BadgeButton: MyBaseButton {
let badgeLabel = UILabel()
override func addSubviews() {
addSubview(badgeLabel)
}
override func setupConstraints() {
badgeLabel.snp.makeConstraints { make in
make.width.height.equalTo(badgeSize)
make.centerX.equalToSuperview().offset(badgeXOffset)
make.centerY.equalToSuperview().offset(badgeYOffset)
}
}
The runTime lable need a frame or constraint.
final class BadgeButton: UIButton {
let badgeLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupView()
}
func setupView() {
self.addSubview(badgeLabel)
badgeLabel.frame = CGRect(x: self.frame.width / 2, y: 0, width: 20, height: 20)
//or you setup runTime Constraint
}
}
On the BadgeButton parent/custom view, try disabling accessibility. Leave it enabled for the UILabel.
After this, back in your XCTestCase, print out the XCUIApplication().debugDescription to see if you're now able to see the UILabel's accessibility information in the app's element hierarchy.
See this post as a possible duplicate:
Xcode UI Tests can't find views that are added programatically

Why touchesBegan listener not called(Swift 4)?

I created simple button - class Btn: UIControl and then add to parent View. Inside my Btn class I have override func touchesBegan.
Why touchesBegan not callend when I tapped to the button?
As I think, my Btn extends UIControl and Btn have to call touchesBegan.
My code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn()
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
Play around with the following code for playgrounds based on your code, and you will understand what's going on. While your control wants to process the touchesBegan callback, it has an UIButton inside of it, which consumes the event itself. In the example below, the inner button is taking one quarter of the Btn size (red part), if you click on it, the "test" text won't get printed. The rest of the Btn space (green part) does not contain any other view that would consume the touchesBegan, thus clicking there will print "test" text.
I recommend you to take a look at the responder chain.
import PlaygroundSupport
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// add my button
let button = Btn(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.addSubview(button)
}
}
class Btn: UIControl {
fileprivate let button: UIButton = {
let swapButton = UIButton()
let size = CGFloat(50)
swapButton.frame.size = CGSize(width: size, height: size)
swapButton.backgroundColor = UIColor.green
return swapButton
}()
override init(frame: CGRect) {
super.init(frame: frame)
addSubview(button)
self.backgroundColor = .red
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
addSubview(button)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
// Why it not called?
print("test")
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = ViewController()

Changing the background color of a custom UIButton?

I've been setting the backgroundColor of buttons by doing self.layer.backgroundColor = someColor.
However, this doesn't seem to work with a custom class? I have this general class that I use:
class DarkButton: BaseButton {
override func layoutSubviews() {
super.layoutSubviews()
self.layer.cornerRadius = 5
self.setTitleColor(UIColor.black, for: .normal)
self.setTitleColor(UIColor.lightGray, for: .highlighted)
self.layer.backgroundColor = UIColor(red:0.77, green:0.77,
blue:0.77, alpha: 1.0).cgColor
self.clipsToBounds = false
}
}
the self.layer.backgroundColor works just fine. Now if I extend it, like so:
class SuperCoolButton: DarkButton {
required init() {
super.init()
self.setUp()
}
required init(spacing: Spacing) {
super.init(spacing: spacing)
self.setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.setUp()
}
func setUp() {
self.generateImage()
self.changeBGColor()
}
func generateImage() {
let image = UIImage(named: "logoSmall") as UIImage?
self.setImage(image, for: .normal)
self.imageEdgeInsets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)
self.imageView?.contentMode = .scaleAspectFit
}
func changeBGColor() {
self.layer.backgroundColor = UIColor.red.cgColor
}
}
the backgroundColor stays that of the DarkButton backgroundColor, however setting the image does indeed work >_>
This is happening because layoutSubviews() implemented in the super class(DarkButton) gets called after setUp method of DarkButton. You need to override layoutSubviews in your class SuperCoolButton and call setUp there instead of at its init method.
override func layoutSubviews() {
super.layoutSubviews()
setUp()
}
EDIT:
I think you should move the code which you have written inside [layoutSubviews][1] of DarkButton class.
layoutSubviews method gets called multiple times and only the code related to the layout of the view should be there.
Subclasses can override this method as needed to perform more precise
layout of their subviews. You should override this method only if the
autoresizing and constraint-based behaviors of the subviews do not
offer the behavior you want.
The ideal place to change layer's corner radius and setTitleColor or background color is either at init of the custom view or at awakeFromNib:(only, if the view is always going to be designed in nib).
Because you want to change the backgroud color of button at some later time. You can simply call your changeBgColor method on SuperCoolButton. Earlier it was not working, because layoutSubviews must be setting the background color back to the default.
Do not call your setUp() method in init instead of this call it in layoutSubviews
class SuperCoolButton: DarkButton {
required init() {
super.init()
//self.setUp()
}
required init(spacing: Spacing) {
super.init(spacing: spacing)
//self.setUp()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//self.setUp()
}
func setUp() {
self.generateImage()
self.changeBGColor()
}
func generateImage() {
let image = UIImage(named: "logoSmall") as UIImage?
self.setImage(image, for: .normal)
self.imageEdgeInsets = UIEdgeInsetsMake(10.0, 10.0, 10.0, 10.0)
self.imageView?.contentMode = .scaleAspectFit
}
func changeBGColor() {
self.layer.backgroundColor = UIColor.red.cgColor
}
override func layoutSubviews() {
super.layoutSubviews()
setUp()
}
}

UIPanGestureRecognnizer in UIView in Swift

I've implemented a UIPanGestureRecognizer in a UIView graph I have made, with the intent of moving it around in the frame of another UIView, which has the same dimensions. However when running it moves all over the frame of the ViewController view (the whole screen) and it not restricted to the smaller frame of its parent UIView I have set up. Here's some screen shots of the graph moving around:
Here's the code for the parent (container) UIView which houses the GraphView class I have made:
class GraphViewDynamic:UIView {
var graphView:GraphView?
init(frame: CGRect, _ backgroudColor:UIColor, _ delegate:GraphViewDelegate) {
super.init(frame:frame)
self.graphView = GraphView.init(frame: frame, backgroudColor, delegate)
self.addSubview(self.graphView!)
}
func graphData(dataSetName setNames: [String], _ lineColors:[UIColor], _ timeInterval:GraphViewTimeInterval) {
if let _ = graphView {
graphView?.graphData(dataSetName: setNames, lineColors, timeInterval)
}
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
and here's the code in my GraphView UIView for the pan gesture handler:
func handlePan(_ sender:UIPanGestureRecognizer) {
sender.view?.center = sender.location(in: sender.view?.superview)
}
Any ideas on how to restrict the panning to the specified frame of the parent UIView of my GraphView (which has the same dimensions)? Thanks for any help.

Custom UIView with tapGesture

I have a drawing app. Inside my VC there are five imageViews with five colors in them. I want to be able to click on the imageView and change the stroke color. It can be easily done if I repeat myself in the viewcontroller by adding gesture Recognizers to each UIImageView and have their individual "selector" function. Such as
func redTapped() {}
func blueTapped() {}
However, I want to be able to make the code more clear by creating a custom class (ColorImageView.Swift) for these ImageViews so that when I assign the class to these buttons, they automatically gets the tap gesture and my VC automatically receives the information about which one is tapped. At the moment, I can get a "imagePressed" printed out for each image that gets assigned to my class. However, I have no way of distinguishing which one were pressed. Below are my code for ColorImageView.Swift
import Foundation
class ColorImageView: UIImageView {
private func initialize() {
let touchGesture = UITapGestureRecognizer(target: self, action: #selector(ColorImageView.imagePressed(_:)))
touchGesture.numberOfTapsRequired = 1
self.userInteractionEnabled = true
self.addGestureRecognizer(touchGesture)
}
override init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
func imagePressed(gestureRecognizer: UITapGestureRecognizer) {
print("image pressed \(gestureRecognizer)")
}
}
My imageView names are red.png, green.png, blue.png...etc
Thanks
You can get the tag easily.It works fine.
func imagePressed(gestureRecognizer: UITapGestureRecognizer)
{
print("image pressed \(gestureRecognizer)")
let tappedImageVIew = gestureRecognizer.view as! UIImageView
print("image pressed \(tappedImageVIew.tag)")
}

Resources