Add GestureRecognizer to a SubView - ios

I have this code. I'm using the GestureRecognizer to call another method but when testing it on my iPhone the TapGestureRecognizer sends nothing. I thought it was a problem with the handleSettingsDismiss so I changed it to print something to know if it was recognizing the tap. But it did nothing and I get nothing on console. I don't know what is wrong.
func showSettingsMenu() {
if let window = UIApplication.shared.keyWindow {
blackView.isUserInteractionEnabled = true
blackView.backgroundColor = UIColor(white: 0, alpha: 0.5)
blackView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.handleSettingsDismiss)))
}
#objc func handleSettingsDismiss(){
UIView.animate(withDuration: 0.5){
self.blackView.alpha = 0
if let window = UIApplication.shared.keyWindow{
self.collectionView.frame = CGRect(x: 0, y: window.frame.height, width: self.collectionView.frame.width, height: self.collectionView.frame.height)
}
}
}

Related

How do you pass a gesture between subviews in UIKit?

I've got a ViewController with three subviews. I'm trying to get them to detect touches in their bounds from a starting point outside their bounds without the user lifting their finger (ie the user dragging into the view). I thought hitTest would do this but it only works for separate taps. I assume this is probably passing a gesture through instead but I've not found out how to implement this.
class SuperViewController: UIViewController {
var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
height: 800))
var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
height: 400))
let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
height: 200))
override func viewDidLoad() {
super.viewDidLoad()
self.view = TestView()
view01.backgroundColor = .orange
view02.backgroundColor = .blue
view03.backgroundColor = .green
self.view.addSubview(view01)
self.view.addSubview(view02)
self.view.addSubview(view03)
}
}
Which produces this
And then I've subclassed UIView for the SuperViewController's view.
class TestView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
guard self.isUserInteractionEnabled, !isHidden, alpha > 0.01 else {return nil}
if self.point(inside: point, with: event) {
for subview in subviews.reversed() {
let hitView = subview.hitTest(point, with: event)
if hitView != nil {
hitView?.backgroundColor = .red
return hitView
}
}
return self
}
return nil
}
}
So each one turns red when the user taps. But ideally I want them to each respond with one drag from the top left corner of the screen to the other.
You can accomplish this with a UIPanGestureRecognizer.
Here's an example below:
class ViewController: UIViewController {
var view01 = UIView(frame: CGRect(x: 0, y: 0, width: 1000,
height: 800))
var view02 = UIView(frame: CGRect(x: 0, y: 0, width: 600,
height: 400))
let view03 = UIView(frame: CGRect(x: 0, y: 0, width: 300,
height: 200))
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view01.backgroundColor = .orange
view02.backgroundColor = .blue
view03.backgroundColor = .green
self.view.addSubview(view01)
self.view.addSubview(view02)
self.view.addSubview(view03)
let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
self.view.addGestureRecognizer(gestureRecognizer)
}
#objc
private func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let view = gestureRecognizer.view else {
return
}
let translation = gestureRecognizer.translation(in: view)
for subview in view.subviews.reversed() {
if let hitView = subview.hitTest(translation, with: nil) {
hitView.backgroundColor = .red
return
}
}
}
}

Tapping a UIImage while it's being animated

I've been trying to be able to tap a UIImage as it animates to the top of my screen and print("Image Tapped"), yet to no success.
override func viewDidLoad() {
super.viewDidLoad()
redBalloon.image = UIImage(named: "redBalloon")
redBalloon.contentMode = .scaleAspectFit
redBalloon.frame = CGRect(x: Int(xOrigin), y: 667, width: Int(redBalloon.frame.size.width), height: Int(redBalloon.frame.size.height))
UIView.animate(withDuration: 5, delay: 0, options: UIImageView.AnimationOptions.allowUserInteraction, animations: {
self.redBalloon.frame = CGRect(x: Int(self.xEnding), y: -192, width: 166, height: 192)
}, completion: {(finished:Bool) in
self.endGame()
})
let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
redBalloon.isUserInteractionEnabled = true
redBalloon.addGestureRecognizer(imageTap)
}
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
// do something when image tapped
print("image tapped")
}
The problem is that the image view is not in the spot where you see it during animation (it's at the endpoint of the animation). So you are not tapping on the image view at the point where it is, and thus the tap is not detected.
Therefore, either you must hit-test the presentation layer or, if you don't want to do that, you must use a UIViewPropertyAnimator instead of calling UIView.animate.
As an example of the first approach, I'll subclass UIImageView. Make your UIImageView an instance of this subclass:
class TouchableImageView : UIImageView {
override func hitTest(_ point: CGPoint, with e: UIEvent?) -> UIView? {
let pres = self.layer.presentation()!
let suppt = self.convert(point, to: self.superview!)
let prespt = self.superview!.layer.convert(suppt, to: pres)
return super.hitTest(prespt, with: e)
}
}
However, personally I think it's a lot simpler to use UIViewPropertyAnimator. In that case, do not make your UIImageView a TouchableImageView! You don't want to do extra hit-test munging. Just let the property animator do all the work:
redBalloon.image = UIImage(named: "redBalloon")
redBalloon.contentMode = .scaleAspectFit
redBalloon.frame = CGRect(x: Int(xOrigin), y: 667, width: Int(redBalloon.frame.size.width), height: Int(redBalloon.frame.size.height))
let anim = UIViewPropertyAnimator(duration: 5, timingParameters: UICubicTimingParameters(animationCurve: .easeInOut))
anim.addAnimations {
self.redBalloon.frame = CGRect(x: Int(xEnding), y: -192, width: 166, height: 192)
}
anim.addCompletion { _ in
self.endGame()
}
let imageTap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
redBalloon.isUserInteractionEnabled = true
redBalloon.addGestureRecognizer(imageTap)
anim.startAnimation()

ViewController's properties are nil

I want to make SideMenu.
my code of HomeViewController:
lazy var sideMenu: SideMenuViewController = {
let menu = SideMenuViewController()
menu.stayViewObj = self
return menu
}()
Menu button's Action:
#objc func handleMenuBtn () {
sideMenu.openMenu()
}
SideMenu's viewDidLoad :
override func viewDidLoad(){
constraintLeadingPropertiesView.constant = -viewSelectProperties.frame.width
}
SideMenu's openMenu method:
func openMenu() {
debugPrint(">>>>openMenu")
let statusBarHeight = UIApplication.shared.statusBarFrame.height
let yCoordinate: CGFloat = statusBarHeight + 44.0
guard let window = UIApplication.shared.keyWindow else { return }
view.backgroundColor = UIColor(white: 0, alpha: 0.5)
view.alpha = 0
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(closeMenu)))
view.frame = window.frame
startFrame = CGRect(x: 0, y: yCoordinate, width: 0, height: window.frame.height - yCoordinate)
view.frame = startFrame!
window.addSubview(view)
UIView.animate(withDuration: 0.7, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.view.alpha = 1
self.view.frame = CGRect(x: 0, y: yCoordinate, width: window.frame.width - 80, height: window.frame.height - yCoordinate)
}, completion: nil)
}
Now, I am getting constraintLeadingPropertiesView=nil and all other properties of SideMenuController are also nil accepting stayViewObj.
so get crash on viewDidLoad.
how to solve this... any suggestion also helpful for me..
Thanks!
need to instantiateInitialViewController instead of create object via lazy Var.
let menu = UIStoryboard.init(name: "Main", bundle: nil).instantiateInitialViewController() as? SideMenuViewController

How to run a loop in ViewDidAppear in Swift?

I want to display a blue and black rectangle alternatively on the launch of the app. I wrote this code. Its compiling fine without any error, but there is no result.
import UIKit
class ViewController: UIViewController {
var mybool = true
override func viewDidAppear(_ animated: Bool) {
while(mybool){
let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
let firstView = UIView(frame: firstFrame)
firstView.backgroundColor = UIColor.blue
view.addSubview(firstView)
let secondView = UIView(frame: firstFrame)
secondView.backgroundColor = UIColor.black
view.addSubview(secondView)
}
}
}
Can anyone tell me where I am going wrong?
Thanks in advance
Do not run an while waiting for a property to change on the main thread, ever! It's the thread that your whole UI relies on. The app will look like it has crashed and iOS will kill it sooner or later.
Use Core Animations:
weak var loadingView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let frame = CGRect(x: 160, y: 240, width: 100, height: 150)
let subview = UIView(frame: frame)
subview.backgroundColor = .black
self.view.addSubview(subview)
UIView.animate(withDuration: 1, delay: 0, options: [.autoreverse, .repeat, .curveEaseInOut], animations: {
subview.backgroundColor = .blue
}, completion: nil)
self.loadingView = subview
}
When you want to stop the animation (for example, when loading has finished) :
func stopAnimation() {
loadingView.layer.removeAllAnimations()
}

UIGestureTap to dismiss view

I am trying to enable UIGestureTap on a custom view. I have a view controller, and in that view controller, when I press a button, a custom view pops up.
var transparentBackground = UIView()
#IBAction func UserViewImage(_ sender: UIButton) -> Void {
self.transparentBackground = UIView(frame: UIScreen.main.bounds)
self.transparentBackground.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
UIApplication.shared.keyWindow!.addSubview(self.transparentBackground)
self.opaqueView = self.setupOpaqueView()
self.transparentBackground.addSubview(opaqueView)
UIApplication.shared.keyWindow!.bringSubview(toFront: self.transparentBackground)
self.view.bringSubview(toFront: transparentBackground)
}
I want to be able to tap on the transparentBackground view and dismiss it. So I have a dismiss function called removeAnimate()
func removeAnimate()
{
UIView.animate(withDuration: 0.25, animations: {
self.transparentBackground.transform = CGAffineTransform(scaleX: 1.3, y: 1.3)
self.transparentBackground.alpha = 0.0;
}, completion:{(finished : Bool) in
if (finished)
{
self.transparentBackground.removeFromSuperview()
}
});
}
So, in viewdidload I enabled the UITapGesture:
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(removeAnimate))
self.transparentBackground.addGestureRecognizer(gestureRecognizer)
self.transparentBackground.isUserInteractionEnabled = true
I know the function removeAnimate works because I used it on a button in the transparentBackground view and it works perfectly. But when I tap on the transparentBackground view it does not dismiss and I am not sure what I am doing wrong
func setupOpaqueView() -> UIView{
let mainView = UIView(frame: CGRect(x: 16, y: 132, width: Int(UIScreen.main.bounds.width-32), height: 403))
mainView.backgroundColor = UIColor.clear
mainView.layer.cornerRadius = 6
self.imageView = UIImageView(frame: CGRect(x: 29, y: 18, width: 274, height: 350))
mainView.addSubview(OKbutton)
mainView.addSubview(self.imageView)
OKbutton.addTarget(self, action: #selector(ThirdWheelViewController.handleOKButtonTapped(_:)), for: .touchUpInside)
return mainView
}
This is an example and hope it helps you:
First of all create a variable:
var customView:UIView!
This is going to be our function for adding a custom view:
#IBAction func customAction(_ sender: AnyObject) {
self.customView = UIView.init(frame: CGRect.init(x: self.view.bounds.width / 2, y: self.view.bounds.height / 2, width: 100, height: 100))
self.customView.backgroundColor = UIColor.red
self.view.addSubview(self.customView)
let tap = UITapGestureRecognizer.init(target: self, action: #selector(self.removeFromSuperView))
tap.numberOfTapsRequired = 1
self.customView.addGestureRecognizer(tap)
}
And finally:
func removeFromSuperView() {
self.customView.alpha = 1.0
self.customView.transform = .identity
UIView.animate(withDuration: 0.3, animations: {
self.customView.alpha = 0.0
self.customView.transform = .init(scaleX: 1.5, y: 1.5)
}) { (finished) in
if !finished {
} else {
self.customView.removeFromSuperview()
}
}
}

Resources