add tapGestureRecognizer to UIImageView in UIView and call method by protocol - ios

I want to make touch recognized on an UIImageView(orange) in an UIView(blue). And hopefully, handle the touch event in a delegate method, which will be used in other classes. However, I can't make touch recognized with below code. How can I implement to recognize touch only in the ImageView(orange).
code:
import UIKit
protocol CustomViewDelegate {
func imageViewTapped()
}
class CustomView: UIView {
var delegate: CustomViewDelegate?
var imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = UIColor.blue
self.imageView.backgroundColor = UIColor.orange
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapped))
imageView.addGestureRecognizer(tapGestureRecognizer)
self.addSubview(imageView)
}
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
func tapped() {
print("tapped method called")
delegate?.imageViewTapped()
}
}
class ViewController: UIViewController, CustomViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))
view.delegate = self
self.view.addSubview(view)
}
func imageViewTapped() {
print("come to view controller")
}
}

UIImageView default userinteraction is false, so enable imageView userinteraction in CustomView init.
imageView.isUserInteractionEnabled = true

Add Different UITapGestureRecognizer to different views and handle as per your requirement . or add tag and access like this
func tapOneAct(sender: UITapGestureRecognizer){
if let tag = sender.view?.tag {
switch tag {
case 1:
println("First View Tapped")
case 2:
println("Second View Tapped")
default:
println("Nothing Tapped")
}
}
}

enable isUserInteractionEnabled property on UIImageView as it defaults to false
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(CustomView.tapped(_:)))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tapGestureRecognizer)
self.addSubview(imageView)
in tapGesture method
func tapgesture(_ sender: UITapGestureRecognizer){
if sender.state == .ended {
let touchLocation: CGPoint = sender.location(in: sender.view?.superview)
if imageView.frame.contains(touchLocation) {
print("tapped method called")
delegate?.imageViewTapped()
}
}
}

Related

How to make UIButton clickable outside frame of it's grand parent view

I have a series of buttons that exist outside the frame of its grand parent view. These buttons are not able to be clicked now, and I am not able to move the buttons, so how can I make them clickable outside the frame of the grand parent view? The button exists inside the a small circular UIView, and that UIView lives outside of the frame of it's parent view. I've tried using a hit test but it is still not triggering
Here is my button:
class ButtonNode: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
}
convenience init(frame: CGRect, agree: Bool) {
self.init(frame: frame)
setupView()
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
func setupView(){
self.setTitle("+10", for: .normal)
self.titleLabel?.font = UIFont.boldSystemFont(ofSize: 12)
self.setTitleColor(.lightGray, for: .normal)
self.isUserInteractionEnabled = true
self.layer.borderWidth = 1.0
self.layer.borderColor = UIColor.lightGray.cgColor
self.layer.cornerRadius = 15
}
}
This is the setup in the parent view:
var rightLastNodeButton:UIButton?
var leftLastNodeButton:UIButton?
leftButton = ButtonNode(frame: CGRect(x: leftX, y: y, width: width, height: height), agree: false)
rightButton = ButtonNode(frame: CGRect(x: rightX, y: y, width: width, height: height), agree: false)
leftButton?.addTarget(self, action: #selector(disagreeUsers), for: .touchUpInside)
rightButton?.addTarget(self, action: #selector(agreeUsers), for: .touchUpInside)
view.addSubview(leftButton!)
view.addSubview(rightButton!)
#objc func agreeUsers(){
delegate?.showParticipantsPressed(agree: true)
}
#objc func disagreeUsers(){
delegate?.showParticipantsPressed(agree: false)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if(rightButton!.frame.contains(point)) {
return rightButton
}
if(leftButton!.frame.contains(point)){
return leftButton
}
return super.hitTest(point, with: event)
}
I had a similar issue and what worked for me was bringing the buttons to the front of the subview. Then it would work.
view.bringSubiewToFront(exampleButton)
or sending the other subviews back
view.sendSubViewToBack(viewlayer)

Swift - Refresh Custom UITabBarController Bar

I have a custom TabBar, with a raised middle button, above the TabBar. When the user has not completed a daily task, the setupIncompleteMiddleButton() should appear, indicating that. However, once the user completes the task, I would like setupCompleteMiddleButton() to appear, indicating the have completed the task. I don't know how to do this - I shouldn't call viewDidLoad() in the controller and when calling it, it does nothing to refresh the view. Refreshing the TabBar does nothing.
This is my TabBar controller:
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
override func viewDidLoad() {
super.viewDidLoad()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
if UserData.hasCompletedDailyTask() == true {
setupCompleteMiddleButton()
} else {
setupIncompleteMiddleButton()
}
}
// Incomplete button
func setupIncompleteMiddleButton() {
let middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Complete button
func setupCompleteMiddleButton() {
let middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
}
Thank you!
You can try this for globally single object of button and just change the image whenever task is complete or not.
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
// TabbarController hode this button
var middleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupButton()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
// taskCompletion is a call back when UserData finish its task
if UserData.taskCompletion {
setupCompleteMiddleButton()
} else {
setupIncompleteMiddleButton()
}
}
func setupButton() {
middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Incomplete button
func setupIncompleteMiddleButton() {
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
}
// Complete button
func setupCompleteMiddleButton() {
// change button color and image
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
Maybe you can try this:
import UIKit
class TabBarController: UITabBarController, UITabBarControllerDelegate {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
}
// TabbarController hode this button
var middleButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// This is where I currently decide which button to show the "complete" one if the task is done, and the incomplete one if not.
self.delegate = self
// taskCompletion is a call back when UserData finish its task
if UserData.taskCompletion {
setupCompleteMiddleButton()
}
setupIncompleteMiddleButton()
}
// Incomplete button
func setupIncompleteMiddleButton() {
// initialize button
middleButton = UIButton(frame: CGRect(x: (self.view.bounds.width / 2) - 25, y: -20, width: 50, height: 50))
middleButton.backgroundColor = UIColor.systemYellow
middleButton.setImage(UIImage(systemName: "sun.max.fill"), for: .normal)
middleButton.imageView?.tintColor = UIColor.white
middleButton.layer.cornerRadius = middleButton.frame.width / 2
middleButton.clipsToBounds = true
self.tabBar.addSubview(middleButton)
middleButton.addTarget(self, action: #selector(self.middleButtonAction), for: .touchUpInside)
self.view.layoutIfNeeded()
}
// Complete button
func setupCompleteMiddleButton() {
// change button color and image
middleButton.backgroundColor = UIColor.systemGreen
middleButton.setImage(UIImage(systemName: "checkmark"), for: .normal)
}
#objc func middleButtonAction(sender: UIButton) {
self.selectedIndex = 1
}
}

rightBarButtonItem appearing in the middle

The rightbarbuttonitem is not appearing on the right side of the navigation bar. I want the navigation bar to look similar to the one in the "App Store"
I have tried doing this in the storyboard and in the code, setting the image content mode, clipping to bounds, and giving it a frame.
I have also been looking at solutions online and none of them have worked for me. Any help or suggestions would be appreciated, thanks.
Here are some screenshots:
import UIKit
class KYSearchBarController: UISearchController {
override init(searchResultsController: UIViewController?) {
super.init(searchResultsController: searchResultsController)
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
// Call in view did appear
func CustomizeSearchBar() {
// Changing color of text in textfield.
let textfieldInsideBar = self.searchBar.value(forKey: "searchField") as? UITextField
textfieldInsideBar?.textColor = .darkGray
// Chaning placeholder
let textfieldLbl = textfieldInsideBar?.value(forKey: "placeholderLabel") as? UILabel
textfieldLbl?.textColor = .darkGray
textfieldLbl?.textAlignment = .center
// Icon customization
let glassIcon = textfieldInsideBar?.leftView as? UIImageView
glassIcon?.image = #imageLiteral(resourceName: "icon")
glassIcon?.image?.withRenderingMode(.alwaysTemplate)
glassIcon?.tintColor = .darkGray
// Centering textfield text
textfieldInsideBar?.textAlignment = .center
let clearButton = textfieldInsideBar?.value(forKey: "clearButton") as! UIButton
clearButton.setImage(UIImage(named: "icon1"), for: .normal)
clearButton.tintColor = .darkGray
}
}
extension UIView {
func MakeRound() {
self.layer.cornerRadius = self.frame.width / 5.0
}
}
class ViewController: UIViewController, UISearchBarDelegate {
let searchController = KYSearchBarController(searchResultsController: nil)
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
let userimage = UIImageView(image: UIImage(named: "person1"))
userimage.frame = CGRect(x: 60, y: 0, width: 50, height: 50)
userimage.clipsToBounds = true
userimage.layer.masksToBounds = true
userimage.contentMode = .scaleAspectFit
userimage.MakeRound()
let rightBarButton = UIBarButtonItem(customView: userimage)
navigationItem.rightBarButtonItem = rightBarButton
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
searchController.CustomizeSearchBar()
}
}
Add the userimage property to make it accessible inside the ViewController.
class ViewController: UIViewController, UISearchBarDelegate {
let searchController = KYSearchBarController(searchResultsController: nil)
let userimage = UIImageView(image: UIImage(named: "person1"))
}
Add the makeRound() function call to viewWillLayoutSubviews().
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
userimage.makeRound()
}
Update the makeRound() function to make a circle.
extension UIView {
func makeRound() {
self.layer.cornerRadius = self.frame.width / 2.0
}
}
Add a method to add the necessary constraints.
func setupConstraints() {
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
guard let navigationBar = self.navigationController?.navigationBar else { return }
navigationBar.addSubview(userimage)
userimage.clipsToBounds = true
userimage.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
userimage.rightAnchor.constraint(equalTo: navigationBar.rightAnchor, constant: -16),
userimage.bottomAnchor.constraint(equalTo: navigationBar.bottomAnchor, constant: -12),
userimage.heightAnchor.constraint(equalToConstant: 40),
userimage.widthAnchor.constraint(equalTo: userimage.heightAnchor)
])
}
Setup a gesture recognizer for the UIImageView and implementation for it.
func setUpGestureRecognizer() {
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(profile))
userimage.isUserInteractionEnabled = true
userimage.addGestureRecognizer(tapGestureRecognizer)
}
#objc func profile() {
// Your implementation
}
Update viewDidLoad() with the method call.
override func viewDidLoad() {
super.viewDidLoad()
// Setup constraints
setupConstraints()
setUpGestureRecognizer()
}
I ran into the same issue when I was using a very large image for my UIBarButtonItem.
Once I resized my image to a smaller size, it was appropriately placed at the right hand side of the navigation bar. It looks like you are having the same issue.
Alternatively, since starting from iOS 11 navigation bar uses autolayout, replacing the line
userimage.frame = CGRect(x: 60, y: 0, width: 50, height: 50)
with the below should also do the trick:
userimage.widthAnchor.constraint(equalToConstant: 50).isActive = true
userimage.heightAnchor.constraint(equalToConstant: 50).isActive = true

Swift: Use of unresolved identifier 'present'

I have a UIView added to UIViewController, and some code of them
ViewController.swift
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let listView = ListView()
self.view.addSubview(listView)
}
ListView.swift
class ListView: UIView {
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(btnGO)
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
lazy var btnGO:(UIButton) = {
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 34))
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)
//some codes
return btn
}()
#objc func btnClicked(sender:UIButton) {
let shareWOW = "some content"
let objectsToShare = [shareWOW]
let activityController = UIActivityViewController(
activityItems: objectsToShare,
applicationActivities: nil)
activityController.popoverPresentationController?.sourceRect = self.frame
activityController.popoverPresentationController?.sourceView = self
activityController.popoverPresentationController?.permittedArrowDirections = .any
present(activityController, animated: true, completion: nil)
}
}
I always get error information Use of unresolved identifier 'present', and I know UIActivityViewController should be placed in UIViewController, and my question is I place the function btnClicked in ViewController.swift, how to call this function in a UIView as ListView.swift? Many thanks
present(_:animated:completion:) method should be used with UIViewController instance. You can't call this method in a UIView subclass. Use delegate to call a method in ViewController from ListView and present UIActivityViewController from the ViewController like this
ViewController.swift
class ViewController: UIViewController, ListViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let listView = ListView()
listView.delegate = self
self.view.addSubview(listView)
}
func btnClicked(sender: UIButton) {
let shareWOW = "some content"
let objectsToShare = [shareWOW]
let activityController = UIActivityViewController(
activityItems: objectsToShare,
applicationActivities: nil)
activityController.popoverPresentationController?.sourceRect = sender.frame
activityController.popoverPresentationController?.sourceView = sender
activityController.popoverPresentationController?.permittedArrowDirections = .any
self.present(activityController, animated: true, completion: nil)
}
}
ListView.swift
protocol ListViewDelegate {
func btnClicked(sender:UIButton)
}
class ListView: UIView {
var delegate: ListViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
self.addSubview(btnGO)
}
convenience init() {
self.init(frame: CGRect.zero)
}
required init(coder aDecoder: NSCoder) {
fatalError("This class does not support NSCoding")
}
lazy var btnGO:(UIButton) = {
let btn = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 34))
btn.addTarget(self, action: #selector(btnClicked), for: .touchUpInside)
//some codes
return btn
}()
#objc func btnClicked(sender:UIButton) {
delegate?.btnClicked(sender: sender)
}
}

Snapkit seems to be blocking all the GestureRecognizers on subviews

I've created a custom UIView which has a UITapGestureRecognizer added to it in it's own init:
class BoxView: UIView, UIGestureRecognizerDelegate {
override init(frame: CGRect) {
super.init(frame: frame)
self.construct()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.construct()
}
convenience init() {
self.init(frame: CGRect.zero)
}
func construct() {
self.backgroundColor = UIColor.whiteColor()
self.frame.size = CGSize(width: 125, height: 125)
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
tapGesture.delegate = self
self.addGestureRecognizer(tapGesture)
}
func handleTap(sender: UITapGestureRecognizer? = nil) {
print("handleTap called!")
}
}
I'm using it in a ViewController, like this:
import Foundation
import UIKit
import AVFoundation
class StartViewController: UIViewController {
private var boxView: BoxView = BoxView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.blackColor()
self.view.addSubview(self.boxView)
/* block: XXX
self.boxView.snp_makeConstraints { (make) -> Void in
make.center.equalTo(self.view)
}
*/
}
}
The gestures don't seem to work as soon as I uncomment the block marked "XXX" in the ViewController which adds the SnapKit constraints. Can someone help me understand what's going on here? Am I using the gesture recognizer correctly?
Looks like I missed an important constraint, adding a "size" constraint on the custom view (BoxView) fixed it.
func construct() {
self.backgroundColor = UIColor.whiteColor()
self.frame.size = CGSize(width: 125, height: 125)
self.snp_makeConstraints { (make) -> Void in
make.size.equalTo(self.frame.size)
}
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("handleTap:"))
tapGesture.delegate = self
self.addGestureRecognizer(tapGesture)
}

Resources