Change existing UIBlurEffect UIBlurEffectStyle programmatically? - ios

I have a Visual Effect View on storyboard connected to my ViewController as an outlet. The effect is burring an ImageView behind it and works great. I'm trying to change the UIBlurEffectStyle from Light to Dark inside a button click IBAction. Any help here would be much appreciated!
#IBOutlet weak var blurView: UIVisualEffectView!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func changeBlurView() {
// This is wrong, but is my best attempt so far.
self.blurView(UIBlurEffectStyle.Dark)
}

While creating my own app I faced to a similar problem. I do not use IB at all, so everything is done programatically. I looked into the UIVisualEffectView.h and it does not provide any effect change on the fly (hopefully this will change in the future).
So here is my solution (I'm using the latest Swift version, so there is an as! operator):
class CustomVisualEffectView : UIVisualEffectView
{
deinit
{
println("UIVisualEffectView will be destroyed.")
}
}
class ViewController: UIViewController
{
var _blurEffect = UIBlurEffect() // global so you can use it for vibrancy effect view as well
var _blurredEffectView = CustomVisualEffectView()
let _someSubView = UIView()
let _someOtherSubView = UIView()
let _effectChanger = UIButton.buttonWithType(.System) as! UIButton
override func viewDidLoad()
{
super.viewDidLoad()
self.view.backgroundColor = UIColor.orangeColor()
/* create a button to change the effect */
_effectChanger.setTitle("Change effect!", forState: UIControlState.Normal)
_effectChanger.frame.size = CGSize(width: 100, height: 20)
_effectChanger.center = self.view.center
_effectChanger.addTarget(self, action: Selector("buttonClicked"), forControlEvents: UIControlEvents.TouchUpInside)
self.view.addSubview(_effectChanger)
/* here is our effect view */
_blurEffect = UIBlurEffect(style: self.randomBlurEfffectStyle())
_blurredEffectView = CustomVisualEffectView(effect: _blurEffect)
self.layoutEffectView()
self.view.addSubview(_blurredEffectView)
/* create two subviews and put them on the effect view */
_someSubView.frame = CGRectMake(10, 10, 10, 10)
_someSubView.backgroundColor = UIColor.redColor()
_blurredEffectView.contentView.addSubview(_someSubView)
_someOtherSubView.frame = CGRectMake(30, 30, 10, 10)
_someOtherSubView.backgroundColor = UIColor.greenColor()
_blurredEffectView.contentView.addSubview(_someOtherSubView)
}
func layoutEffectView()
{
_blurredEffectView.frame.size = CGSize(width: 100, height: 80)
_blurredEffectView.center = CGPointMake(_effectChanger.center.x, _effectChanger.center.y - 50)
}
func buttonClicked()
{
var tempArray = [AnyObject]()
/* get all subviews from the effect view */
for view in _blurredEffectView.contentView.subviews
{
tempArray.append(view)
view.removeFromSuperview()
}
/* modify your effect view */
_blurEffect = UIBlurEffect(style: self.randomBlurEfffectStyle())
/* IMPORTANT: so the old effect view can be destroyed by arc */
_blurredEffectView.removeFromSuperview()
_blurredEffectView = CustomVisualEffectView(effect: _blurEffect)
/* I think this will be really tricky if you will use auto layout */
self.layoutEffectView()
self.view.addSubview(_blurredEffectView)
/* put all subviews back to the effect view*/
for view in tempArray
{
_blurredEffectView.contentView.addSubview(view as! UIView)
}
}
func randomBlurEfffectStyle() -> UIBlurEffectStyle
{
let randomBlurEffectStyle : UIBlurEffectStyle
switch Int(arc4random_uniform(3)) // [0,1,2]
{
case 0:
randomBlurEffectStyle = .ExtraLight
case 1:
randomBlurEffectStyle = .Light
default:
randomBlurEffectStyle = .Dark
}
return randomBlurEffectStyle
}
}

Related

Best way to position UIToolbar programmatically (with or without UIToolbarDelegate)?

I'm implementing in Playgound a segmented control underneath the navigation bar.
This seems to be a classic problem, which has been asked:
UISegmentedControl below UINavigationbar in iOS 7
Add segmented control to navigation bar and keep title with buttons
In the doc of UIBarPositioningDelegate, it says,
The UINavigationBarDelegate, UISearchBarDelegate, and
UIToolbarDelegate protocols extend this protocol to allow for the
positioning of those bars on the screen.
And In the doc of UIBarPosition:
case top
Specifies that the bar is at the top of its containing view.
In the doc of UIToolbar.delegate:
You may not set the delegate when the toolbar is managed by a
navigation controller. The default value is nil.
My current solution is as below (the commented-out code are kept for reference and convenience):
import UIKit
import PlaygroundSupport
class ViewController : UIViewController, UIToolbarDelegate
{
let toolbar : UIToolbar = {
let ret = UIToolbar()
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
// toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: 44
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .topAttached
}
}
//class Toolbar : UIToolbar {
// override var barPosition: UIBarPosition {
// return .topAttached
// }
//}
let vc = ViewController()
vc.title = "Try"
vc.view.backgroundColor = .red
// Another way to add toolbar...
// let segmented = UISegmentedControl(items: ["Good", "Bad"])
// let barItem = UIBarButtonItem(customView: segmented)
// vc.toolbarItems = [barItem]
// Navigation Controller
let navVC = UINavigationController(navigationBarClass: UINavigationBar.self, toolbarClass: UIToolbar.self)
navVC.pushViewController(vc, animated: true)
navVC.preferredContentSize = CGSize(width: 375, height: 640)
// navVC.isToolbarHidden = false
// Page setup
PlaygroundPage.current.liveView = navVC
PlaygroundPage.current.needsIndefiniteExecution = true
As you can see, this doesn't use a UIToolbarDelegate.
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
#Leo Natan's answer in the first question link above mentioned the UIToolbarDelegate, but it seems the toolbar is placed in Interface Builder.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
Try this
UIView *containerVw = [[UIView alloc] initWithFrame:CGRectMake(0, 64, 320, 60)];
containerVw.backgroundColor = UIColorFromRGB(0xffffff);
[self.view addSubview:containerVw];
UIView *bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, 124, 320, 1)];
bottomView.backgroundColor = [UIColor grayColor];
[self.view addSubview:bottomView];
UISegmentedControl *sg = [[UISegmentedControl alloc] initWithItems:#[#"Good", #"Bad"]];
sg.frame = CGRectMake(10, 10, 300, 40);
[view addSubview:sg];
for (UIView *view in self.navigationController.navigationBar.subviews) {
for (UIView *subView in view.subviews) {
[subView isKindOfClass:[UIImageView class]];
subView.hidden = YES;
}
}
By setting the toolbar's delegate and by having the delegate method return .top, you get the normal shadow at the bottom of the toolbar. If you also adjust the toolbars frame one point higher, it will cover the navbar's shadow and the final result will be what appears to be a taller navbar with a segmented control added.
class ViewController : UIViewController, UIToolbarDelegate
{
lazy var toolbar: UIToolbar = {
let ret = UIToolbar()
ret.delegate = self
let segmented = UISegmentedControl(items: ["Good", "Bad"])
let barItem = UIBarButtonItem(customView: segmented)
ret.setItems([barItem], animated: false)
return ret
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(toolbar)
toolbar.delegate = self
}
override func viewDidLayoutSubviews() {
toolbar.frame = CGRect(
x: 0,
y: navigationController?.navigationBar.frame.height - 1 ?? 0,
width: navigationController?.navigationBar.frame.width ?? 0,
height: toolbar.frame.height
)
}
func position(for bar: UIBarPositioning) -> UIBarPosition {
return .top
}
}
How does a UIToolbarDelegate (providing the position(for:)) come into play in this situation? Since we can always position ourselves (either manually or using Auto Layout), what's the use case of a UIToolbarDelegate?
I sincerely do not know how the UIToolbarDelegate comes into play, if you change the UINavigationController.toolbar it will crashes with "You cannot set UIToolbar delegate managed by the UINavigationController manually", moreover the same will happen if you try to change the toolbar's constraint or its translatesAutoresizingMaskIntoConstraints property.
Moreover, if we don't use UIToolbarDelegate here, why don't we just use a plain UIView instead of a UIToolbar?
It seems to be a reasonable question. I guess the answer for this is that you have a UIView subclass which already has the behaviour of UIToolbar, so why would we create another class-like UIToolbar, unless you just want some view below the navigation bar.
There are 2 options that I'm aware of.
1) Related to Move UINavigationController's toolbar to the top to lie underneath navigation bar
The first approach might help when you have to show the toolbar in other ViewControllers that are managed by your NavigationController.
You can subclass UINavigationController and change the Y-axis position of the toolbar when the value is set.
import UIKit
private var context = 0
class NavigationController: UINavigationController {
private var inToolbarFrameChange = false
var observerBag: [NSKeyValueObservation] = []
override func awakeFromNib() {
super.awakeFromNib()
self.inToolbarFrameChange = false
}
override func viewDidLoad() {
super.viewDidLoad()
observerBag.append(
toolbar.observe(\.center, options: .new) { toolbar, _ in
if !self.inToolbarFrameChange {
self.inToolbarFrameChange = true
toolbar.frame = CGRect(
x: 0,
y: self.navigationBar.frame.height + UIApplication.shared.statusBarFrame.height,
width: toolbar.frame.width,
height: toolbar.frame.height
)
self.inToolbarFrameChange = false
}
}
)
}
override func setToolbarHidden(_ hidden: Bool, animated: Bool) {
super.setToolbarHidden(hidden, animated: false)
var rectTB = self.toolbar.frame
rectTB = .zero
}
}
2) You can create your own UIToolbar and add it to view of the UIViewController. Then, you add the constraints to the leading, trailing and the top of the safe area.
import UIKit
final class ViewController: UIViewController {
private let toolbar = UIToolbar()
private let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Op 1", "Op 2"])
control.isEnabled = false
return control
}()
override func loadView() {
super.loadView()
setupToolbar()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.hideBorderLine()
}
private func setupToolbar() {
let barItem = UIBarButtonItem(customView: segmentedControl)
toolbar.setItems([barItem], animated: false)
toolbar.isTranslucent = false
toolbar.isOpaque = false
view.addSubview(toolbar)
toolbar.translatesAutoresizingMaskIntoConstraints = false
toolbar.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
toolbar.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
toolbar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
}
}
private extension UINavigationBar {
func showBorderLine() {
findBorderLine().isHidden = false
}
func hideBorderLine() {
findBorderLine().isHidden = true
}
private func findBorderLine() -> UIImageView! {
return self.subviews
.flatMap { $0.subviews }
.compactMap { $0 as? UIImageView }
.filter { $0.bounds.size.width == self.bounds.size.width }
.filter { $0.bounds.size.height <= 2 }
.first
}
}

becomeFirstResponder not showing from viewDidLoad

Here I have a simple example. im calling FirstResponder in viewDidLoad. But accessory view only shows up after tapping the screen. Why isn't it showing from the start?
class TestViewController: MainPageViewController {
private let accessoryView = UIView() //TextInputView() // MessageInputAccessoryView()
override var inputAccessoryView: UIView {
return accessoryView
}
override var canBecomeFirstResponder: Bool { return true }
override func viewDidLoad() {
super.viewDidLoad()
accessoryView.backgroundColor = .red
accessoryView.frame = CGRect(x: 0, y: 0, width: 300, height: 50)
self.becomeFirstResponder()
// Do any additional setup after loading the view.
let tap = UITapGestureRecognizer(target: self, action: #selector(tappo))
self.view.isUserInteractionEnabled = true
self.view.addGestureRecognizer(tap)
tappo()
}
func tappo() {
self.becomeFirstResponder()
}
}
viewWillAppear is a better place to put the becomesFirstResponder. Try that.
So something was resigning my first responder (as I was using UIPageViewController). So I've added this to my UIViewControler :
override var canResignFirstResponder: Bool { return false }
That's it. Cheers!

Making a UIView update behind another one in the same view controller

I'm working in swift to make an app the uses the cocoapod framework BBSlideoutMenu to display a menu. I am also using the cocoapod framework ChameleonFramework. What I'm trying to do is make the hamburger button that I'm using change colour when it is opened. I haven't yet implemented it, but I also want to make the bar on top transparent. I've recorded it here so you can see what is happening. Basically, the view only gets updated when I slide away the menu.
Disclaimer: I am aware that using a hamburger menu is viewed as bad code design, unfortunately it is what I need in this app.
Here is my code:
import UIKit
import BBSlideoutMenu
import ChameleonFramework
class ViewController: UIViewController, BBSlideoutMenuDelegate {
#IBOutlet var slideMenu: BBSlideoutMenu!
var button: HamburgerButton! = nil
var menuOpen = false;
let screenSize: CGRect = UIScreen.mainScreen().bounds
let topBar = UIView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height * 0.1))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib
view.backgroundColor = UIColor.whiteColor()
topBar.backgroundColor = FlatRed()
button = HamburgerButton(frame: CGRectMake(0, 20, 54, 54))
button.addTarget(self, action: #selector(toggleMenu(_:)), forControlEvents:.TouchUpInside)
topBar.addSubview(button)
view.addSubview(topBar)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
slideMenu.setupEdgePan()
slideMenu.slideDirection = .Right
slideMenu.shrinkAmount = 0
slideMenu.slideTravelPercent = 0.87
slideMenu.menuOffset = 0
slideMenu.zoomFactor = 1
slideMenu.springEnabled = false
slideMenu.backgroundColor = FlatRed()
slideMenu.delegate = self
slideMenu.setupEdgePan()
}
func toggleMenu(sender : HamburgerButton!) {
if(menuOpen) {
slideMenu.dismissSlideMenu(animated: true, time: nil)
} else {
slideMenu.presentSlideMenu(true) { () -> Void in
//Runs after menu is presented
}
}
}
func didPresentBBSlideoutMenu(menu: BBSlideoutMenu) {
menuOpen = true
button.changeColor(UIColor.blackColor())
}
func didDismissBBSlideoutMenu(menu: BBSlideoutMenu) {
menuOpen = false
button.changeColor(UIColor.whiteColor())
}
}
I am using this hamburger menu button, which has been created with CoreGraphics and QuartzCore, and have added the following function for change colour.
func changeColor(color: UIColor) {
for layer in [ self.topStroke, self.middleStroke, self.bottomStroke ] {
layer.fillColor = nil
layer.strokeColor = color.CGColor
layer.lineWidth = 4
layer.miterLimit = 4
layer.lineCap = kCALineCapRound
layer.masksToBounds = true
let strokingPath = CGPathCreateCopyByStrokingPath(layer.path, nil, 4, .Round, .Miter, 4)
layer.bounds = CGPathGetPathBoundingBox(strokingPath)
layer.actions = [
"strokeStart": NSNull(),
"strokeEnd": NSNull(),
"transform": NSNull()
]
self.layer.addSublayer(layer)
}
}
Edit: I have tried using setNeedsDisplay on the button, the topBar, and both of them consecutively in the functions toggleMenu, didPresentBBSlideoutMenu and didDismissBBSlideoutMenu and it didn't work
I have also tried calling it on the actual view (self.view)
Try calling setNeedsDisplay the button.

UITextfield showing and dismissing very quickly in UIPageViewController (Swift)

I have a UIPageViewController as walkthrough presentation for my app. The walkthrough has basically 5 pages in total. Page 4 has a UITextField programmatically set. Don't mind the other objects like labels or Images.
The problem I have is that when the user reaches page 4, The user is presented with a Textfield which i'd like the keyboard to show up automatically via a becomeFirstResponder.
When I load this 4th view with the textfield, It shows up very quickly on the simulator and on my device and then goes back down. It basically shows up via the becomeFirstResponder code but somehow then resigns within 1 second.
I have tried this programmatically and also via the storyboard UITextfield to see if that made a difference but both seem to not work.
The code that executes a label is where I also executed the UITextField programmatically but for this example, I removed it and used an IBOutlet instead as you can see.
Also, in this case 3 I would put the becomeFirstResponder to activate the keyboard but it still did not work as expected.
Right now I left it in the viewWillAppear method as you can see.
Here is my code for this specific scenario:
import UIKit
class WalkthroughViewController: UIViewController {
#IBOutlet var headingLabel:UILabel!
#IBOutlet var contentLabel:UILabel!
#IBOutlet var contentImageView:UIImageView!
#IBOutlet var pageControl:UIPageControl!
#IBOutlet var forwardButton:UIButton!
// This was added via storyboard via drag and drop
#IBOutlet var nameField: UITextField!
// This is a UITextfield programmatically
let textField = UITextField(frame: CGRectMake(20, 200.0, 330.0, 40.0)) // x=x-cord, y=y-cord, Width, Height
// May 2 Updates
func textFieldFunc() {
textField.textAlignment = NSTextAlignment.Center
textField.textColor = UIColor.wetAsphaltColor()
textField.backgroundColor = UIColor.whiteColor()
textField.font = UIFont(name: "avenir", size: 21)
textField.borderStyle = UITextBorderStyle.None
textField.autocapitalizationType = UITextAutocapitalizationType.Words // If you need any capitalization
textField.becomeFirstResponder()
self.view.addSubview(textField)
}
func nameLabel() {
let label = UILabel(frame: CGRectMake(15, 180, 265, 33))
label.center = CGPointMake(185, 160)
label.textColor = UIColor.cloudsColor()
label.font = UIFont(name: "avenir", size: 30)
label.textAlignment = NSTextAlignment.Center
label.text = "Whats your name?"
self.view.addSubview(label)
}
#IBAction func submit(sender: AnyObject) {
// Thisis going to handle the name later and it will then reguster the user for later use within the app
}
var index = 0
var heading = ""
var imageFile = ""
var content = ""
override func viewDidLoad() {
super.viewDidLoad()
headingLabel.text = heading
contentLabel.text = content
contentImageView.image = UIImage(named: imageFile)
pageControl.currentPage = index
// 0...2
switch index {
case 0...2: forwardButton.setTitle("NEXT", forState: UIControlState.Normal)
nameField.hidden = true
// 3
case 3:
nameLabel()
nameField.hidden = false
case 4:
forwardButton.setTitle("DONE", forState: UIControlState.Normal)
default: break
}
print("Index: \(index)")
}
#IBAction func nextButtonTapped(sender: UIButton) {
switch index {
case 0...3: // 2
let pageViewController = parentViewController as! WalkthroughPageViewController
pageViewController.forward(index)
case 4: // 3
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setBool(true, forKey: "finishedWalkedThrough")
dismissViewControllerAnimated(true, completion: nil)
default: break
}
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if nameField.hidden == false {
nameField.becomeFirstResponder()
}
}
}
My question is, how can I remedy this so that when the 4th page is presented in a slide through manner, the UITextfield can present the keyboard like a normal view?
As I saw in a very similar post, This actually worked. I managed to solve it by using this:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if index == 3 {
dispatch_async(dispatch_get_main_queue(), {() -> Void in
let strongSelf: WalkthroughViewController = self
strongSelf.textField.becomeFirstResponder()
})
}
}
Now when I scroll to the 4th page (3rd from 0), It loads the keyboard with the .becomeFirstResponder as a normal UITextField would.
dispatch_after(DISPATCH_TIME_NOW, dispatch_get_main_queue(), ^{
[self.yourTextField becomeFirstResponder];
});
For Swift 3:
DispatchQueue.main.async(execute: {() -> Void in
let strongSelf: WalkthroughViewController = self
strongSelf. textField.becomeFirstResponder()
})
I didn't have to specify the index position of the ViewController

Tap Gesture Recognizer not received in custom UIView embedded in super view

I am trying to create a custom UIView/Scrollview named MyScrollView that contains a few labels (UILabel), and these labels receive tap gestures/events in order to respond to user's selections .
In order to make the tap event work on the UILabels, I make sure they all have userIteractionEnabled = true and I created a delegate as below:
protocol MyScrollViewDelegate {
func labelClicked(recognizer: UITapGestureRecognizer)
}
The custom UIView is being used in ScrollViewController that I created, this ScrollViewController implements the delegate method as well:
import UIKit
import Neon
class ScrollViewController: UIViewController, MyScrollViewDelegate {
var curQuestion: IPQuestion?
var type: QuestionViewType?
var lastClickedLabelTag: Int = 0 //
init(type: QuestionViewType, question: IPQuestion) {
super.init(nibName: nil, bundle: nil)
self.curQuestion = question
self.type = type
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func loadView() {
view = MyScrollView(delegate: self, q: curQuestion!)
view.userInteractionEnabled = true
}
}
// implementations for MyScrollViewDelegate
extension ScrollViewController {
func labelTitleArray() -> [String]? {
print("labelTitleArray called in implemented delegate")
return ["Comments", "Answers"]
}
func labelClicked(recognizer: UITapGestureRecognizer) {
print("labelClicked called in implemented delegate")
let controller = parentViewController as? ParentViewController
controller?.labelClicked(recognizer)
lastClickedLabelTag = recognizer.view!.tag
}
}
// MARK: - handle parent's ViewController event
extension QuestionDetailViewController {
func updateActiveLabelsColor(index: Int) {
print("updating active labels color: \(index)")
if let view = view as? MyScrollView {
for label in (view.titleScroll.subviews[0].subviews as? [UILabel])! {
if label.tag == index {
label.transform = CGAffineTransformMakeScale(1.1,1.1)
label.textColor = UIColor.purpleColor()
}
else {
label.transform = CGAffineTransformMakeScale(1,1)
label.textColor = UIColor.blackColor()
}
}
}
}
}
This above ScrollViewController is added, as a child view controller to the parent view controller, and positioned to the top part of the parent's view:
override func viewDidLoad() {
super.viewDidLoad()
self.automaticallyAdjustsScrollViewInsets = false
self.view.backgroundColor = UIColor.whiteColor()
addChildViewController(scrollViewController) // added as a child view controller here
view.addSubview(scrollViewController.view) // here .view is MyScrollView
scrollViewController.view.userInteractionEnabled = true
scrollViewController.view.anchorToEdge(.Top, padding: 0, width: view.frame.size.width, height: 100)
}
The app can load everything up in the view, but the tap gesture/events are not passed down to the labels in the custom MyScrollView. For this, I did some google search and have read Event Delivery: Responder Chain on Apple Developer website and did a hit test as well. The hitTest function below can be triggered in the MyScrollView:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
print("hit test started, point: \(point), event: \(event)")
return self
}
My observations with the hitTest is that the touchesBegan() and touchesEnded() methods are triggered in the view only when the hitTest function is there. Without hitTest, both functions do not get called with taps.
but no luck getting the UILabel to respond to Tap Gestures. So I am reaching out to experts on SO here. Thanks for helping!
I think I found out the reason why the UILabel did not respond to tapping after much struggle: the .addGestureRecognizer() method to the label was run in the init() method of my custom UIView component, which is wrong, because the view/label may not have been rendered yet. Instead, I moved that code to the lifecycle method layoutSubviews(), and everything started to work well:
var lastLabel: UILabel? = nil
for i in 0..<scrollTitleArr.count {
let label = UILabel()
label.text = scrollTitleArr[i] ?? "nothing"
print("label: \(label.text)")
label.font = UIFont(name: "System", size: 15)
label.textColor = (i == 0) ? MaterialColor.grey.lighten2 : MaterialColor.grey.darken2
label.transform = (i == 0) ? CGAffineTransformMakeScale(1.1, 1.1) : CGAffineTransformMakeScale(0.9, 0.9)
label.sizeToFit()
label.tag = i // for tracking the label by tag number
label.userInteractionEnabled = true
label.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.labelClicked(_:))))
titleContainer.addSubview(label)
if lastLabel == nil {
label.anchorInCorner(.TopLeft, xPad: 0, yPad: 0, width: 85, height: 40)
// label.anchorToEdge(.Left, padding: 2, width: 85, height: 40)
} else {
label.align(.ToTheRightMatchingTop, relativeTo: lastLabel!, padding: labelHorizontalGap, width: 85, height: 40)
}
lastLabel = label
}
In addition, I don't need to implement any of the UIGestureRecognizer delegate methods and I don't need to make the container view or the scroll view userInteractionEnabled. More importantly, when embedding the custom UIView to a superview, I configured its size and set clipsToBounds = true.
I guess I should have read more UIView documentation on the Apple Developer website. Hope this will help someone like me in the future! Thanks to all!
You have to set the property userInteractionEnabled = YES.
For some reason, my simulator was frozen or something when the tap gesture recognizer wasn't working. So, when I restarted the app, then it all worked again. I don't know if this applies here, but that was the fix for me.

Resources