I am facing an issue that occurs only on real device, specifically, I am trying to trigger gesture recognizer and expand the image with animation by tapping on image profile view which is inside of a custom UIView that is assigned to navigationItem.titleView. The image profile view is outside of the parent view frame, so I am aware that point(inside:with:) function needs to be implemented and return true in order for gesture to be recognized, however it is still not recognize, so I tried to call the gesture selector function from inside point(inside:with:). This works as expected on simulator however, doesn’t work on real device.
This is the output on the simulator:
I also tried to use hitTest(_:with:) without any luck.
Any suggestions on how to debug this kind of issue would be appreciated.
Code for testing:
import UIKit
class ViewController: UIViewController {
let button = UIButton()
override func viewDidLoad() {
super.viewDidLoad()
setupButton()
}
func setupButton() {
view.addSubview(button)
view.backgroundColor = .brown
button.setTitle("next", for: .normal)
button.backgroundColor = .green
button.translatesAutoresizingMaskIntoConstraints = false
button.addTarget(self, action: #selector(pushToNavVC), for: .touchUpInside)
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.centerYAnchor.constraint(equalTo: view.centerYAnchor),
button.widthAnchor.constraint(equalToConstant: 50),
button.heightAnchor.constraint(equalToConstant: 50),
])
}
@objc func pushToNavVC() {
let secondVc = SecondViewController()
navigationController?.pushViewController(secondVc, animated: true)
}
}
class SecondViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .brown
setupNavigationBar()
}
private func setupNavigationBar() {
let titleView = CustomTitleView(name: "John Doe", lastSeen: "Last seen 5 min ago")
navigationItem.titleView = titleView
}
}
class CustomTitleView: UIView {
let nameLabel: UILabel
let lastSeenLabel: UILabel
let profileImage: UIImageView!
private var temporaryDimmView: UIView!
private var temporaryImageView: UIView!
init(name: String, lastSeen: String) {
nameLabel = UILabel()
lastSeenLabel = UILabel()
profileImage = UIImageView()
super.init(frame: .zero)
setupViews(name: name, lastSeen: lastSeen)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews(name: String, lastSeen: String) {
nameLabel.text = name
nameLabel.font = UIFont.boldSystemFont(ofSize: 16)
lastSeenLabel.text = lastSeen
lastSeenLabel.font = UIFont.systemFont(ofSize: 12)
lastSeenLabel.textColor = .gray
profileImage.image = UIImage(named: "your_image_name_here")
profileImage.translatesAutoresizingMaskIntoConstraints = false
profileImage.isUserInteractionEnabled = true
let imageTapGesture = UITapGestureRecognizer(target: self, action: #selector(animateProfileImage))
profileImage.addGestureRecognizer(imageTapGesture)
let stackView = UIStackView(arrangedSubviews: [nameLabel, lastSeenLabel])
stackView.axis = .vertical
stackView.alignment = .center
addSubview(stackView)
addSubview(profileImage)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: topAnchor),
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 40),
stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -40),
profileImage.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 50),
profileImage.widthAnchor.constraint(equalToConstant: 30),
profileImage.heightAnchor.constraint(equalToConstant: 30),
profileImage.centerYAnchor.constraint(equalTo: centerYAnchor),
widthAnchor.constraint(equalToConstant: 200)
])
}
@objc func animateProfileImage() {
guard let window = window else { return }
temporaryDimmView = setupTemporaryDimmView(withFrame: window.frame)
temporaryImageView = setupTemporaryImageView()
UIView.animate(withDuration: 0.5, animations: {
self.profileImage.isHidden = true
self.temporaryImageView.center = window.center
self.temporaryImageView.transform = CGAffineTransform(scaleX: 8, y: 8)
self.temporaryDimmView.alpha = 0.85
})
}
@objc func dismissProfileImage(_ sender: UITapGestureRecognizer) {
let imageFrame = profileImage.convert(profileImage.bounds, to: window)
UIView.animate(withDuration: 0.5) {
self.temporaryDimmView.alpha = 0
self.temporaryImageView.transform.a = 1
self.temporaryImageView.transform.d = 1
self.temporaryImageView.frame.origin = imageFrame.origin
} completion: { _ in
self.temporaryDimmView.removeFromSuperview()
self.temporaryImageView.removeFromSuperview()
self.temporaryDimmView = nil
self.temporaryImageView = nil
self.profileImage.isHidden = false
}
}
/// As of my knowledge, this function should trigger gesture recognizer
/// that is attached to views which are outside of their parent/superview frame.
/// However, it does not in this case, so i call animateProfileImage() inside it
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool
{
if super.point(inside: point, with: event) {return true}
for subview in subviews {
let subviewPoint = subview.convert(point, from: self)
if subview.point(inside: subviewPoint, with: event) {
animateProfileImage()
return true
}
}
return false
}
}
// temp functions that are created when image animation starts
extension CustomTitleView {
private func setupTemporaryImageView() -> UIView {
let imageFrame = profileImage.convert(profileImage.bounds, to: window)
let animatedImageView = UIImageView(frame: imageFrame)
animatedImageView.image = profileImage.image
animatedImageView.contentMode = .scaleAspectFill
window?.addSubview(animatedImageView)
return animatedImageView
}
private func setupTemporaryDimmView(withFrame frame: CGRect) -> UIView {
let dimmView = UIView(frame: frame)
dimmView.backgroundColor = .black
dimmView.alpha = 0
window?.addSubview(dimmView)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissProfileImage))
dimmView.addGestureRecognizer(tapGesture)
return dimmView
}
}