UIViewController Subclass does not recognize UITapGesture - ios

With the intention to slim down my viewcontroller a little bit, i want to move the ui elements and corresponding functions into a subclass. but then my gestures don't work. how can i solve this?
MyViewController.swift
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
SubclassMyViewController.swift
class SubclassMyViewController: MyViewController {
func setupUserInterface(view: UIView) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
// ADD GESTURE
let tap = UITapGestureRecognizer(target: self, action: #selector(doSomething))
imageView.isUserInteractionEnabled = true
imageView.addGestureRecognizer(tap)
return imageView
}()
}
Putting everything into the Viewcontroller does work. if I split it in two classes, the gesture won't be recognized.. Thanks!

I think you're confusing some things here.
SubclassMyViewController().setupUserInterface(view: view) this line creates an instance of SubclassMyViewController, which in your code sample owns the image view. Because you don't have any references to the created subclass however, it will die as soon as this line is done executing.
You could accomplish your goal by creating a static helper class instead. Here's how that would look.
class MyViewController: UIViewController {
var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
MyViewControllerSetupHelper.setupUserInterface(viewController: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
class MyViewControllerSetupHelper {
static func setupUserInterface(viewController: MyViewController) {
let view = viewController.view!
let imageView = getLogoImageView()
viewController.imageView = imageView
let tapGesture = UITapGestureRecognizer(target: viewController, action: #selector(MyViewController.doSomething))
imageView.addGestureRecognizer(tapGesture)
view.addSubview(imageView)
NSLayoutConstraint.activate([
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
imageView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
imageView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5)
])
}
static func getLogoImageView() -> UIImageView {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
imageView.isUserInteractionEnabled = true
return imageView
}
}

SubclassMyViewController().setupUserInterface(view: view) is creating a new instance of subclass, but the reference of the instance is not stored.
Swift will deallocate the class if there is no reference (refer to ARC).
The lazy var logoImage is adding a GestureRecognizer which points to the doSomething() of the SubclassMyViewController-instance, which is deallocated immediately.
It would work if you do this (THIS IS NOT GOOD):
class ViewController: UIViewController {
var x: SubclassMyViewController!
override func viewDidLoad() {
super.viewDidLoad()
x = SubclassMyViewController()
x.setupUserInterface(view: view)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
You should fix your design, because:
ViewController is accessing SubclassMyViewController, which is, however, a subclass of ViewController

You doing really strange things. Gesture recognizer added to SubclassMyViewController instance. Which seams removed after creation.
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view, vc: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
}
class SubclassMyViewController: MyViewController {
func setupUserInterface(view: UIView, vc: UIViewController) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
let tap = UITapGestureRecognizer(target: vc, action: #selector(doSomething))
logoImage.isUserInteractionEnabled = true
logoImage.addGestureRecognizer(tap)
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
}
P.S. code is really dirty.. don't do like this
Edit
Probably this is what you wanna do:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
SubclassMyViewController().setupUserInterface(view: view, vc: self)
}
#objc func doSomething() {
log.info("logo was tapped")
}
func setupUserInterface(view: UIView) {
// ...
view.addSubview(logoImage)
logoImage.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logoImage.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
logoImage.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.5).isActive = true
let tap = UITapGestureRecognizer(target: self, action: #selector(doSomething))
logoImage.isUserInteractionEnabled = true
logoImage.addGestureRecognizer(tap)
}
lazy var logoImage: UIImageView = {
let imageView = UIImageView()
imageView.image = UIImage(named: "logo")
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.contentMode = .scaleAspectFit
return imageView
}()
}

Related

How to fetch the width and height of a programatically added view in Swift/ iOS?

I have added a UIView and a UIImageView programatically, but I need its width and height.
Is it possible to fetch it, if yes, please share how to fetch it.
Code:
let View1: UIView = {
let viewView = UIView()
viewView.translatesAutoresizingMaskIntoConstraints = false
viewView.contentMode = .scaleAspectFit
print(UserDefaults.standard.bool(forKey: "backgroundColourSelected"))
if UserDefaults.standard.bool(forKey: "backgroundColourSelected") {
viewView.backgroundColor = self.viewColor
}else {
viewView.backgroundColor = .white
}
viewView.clipsToBounds = true
return viewView
}()
self.canvasView.addSubview(View1)
View1.centerXAnchor.constraint(equalTo: canvasView.centerXAnchor, constant: 0).isActive = true
View1.centerYAnchor.constraint(equalTo: canvasView.centerYAnchor, constant: 0).isActive = true
View1.widthAnchor.constraint(equalTo: canvasView.widthAnchor, constant: 0).isActive = true
View1.heightAnchor.constraint(equalTo: canvasView.widthAnchor, multiplier: aspectRatio).isActive = true
You need to know the life cycle of view controller.
To get height of any view use viewDidLayoutSubviews.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
print(view1.frame.size)
}
size property will print both final height and width.
use override func viewWillLayoutSubviews() for ViewController and override func layoutSubviews() for UIView.
Example : - for UIViewControllers
override func viewWillLayoutSubviews() {
print(yourView.frame.height)
}
for UIViews
override func layoutSubviews() {
print(yourView.frame.height)
print(yourView.frame.width)
}
Since your view is inside the function just move it out
class YourController / YourView {
private let imgView: UIImageView = {
let iv = UIImageView()
// do whatever you want with your image using iv. eg. iv.contentMode = .scaleAspectFit or iv.image = UIImage(named: "custom_img")
return iv
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(imgView)
//set constraints for your
}
// then use layoutSubViews(for views) / viewWillLayoutSubViews (for controller) and calculate the height
}

Custom resuable UIActivityIndicatorView Class programmatically

I'm new in making views programmatically.
I'm trying to make UIActivityIndicatorView class to make it reusable for me.
This is the class I made:
class ActivityIndicator: UIActivityIndicatorView {
let indicator = UIActivityIndicatorView()
let indicatorContainer = UIView()
func setupIndicatorView() {
indicatorContainer.isHidden = false
indicator.isHidden = false
indicator.style = .large
indicator.color = .white
indicator.startAnimating()
indicator.hidesWhenStopped = true
indicator.translatesAutoresizingMaskIntoConstraints = false
indicatorContainer.backgroundColor = .darkGray
indicatorContainer.alpha = 0.7
indicatorContainer.layer.cornerRadius = 8.0
indicatorContainer.translatesAutoresizingMaskIntoConstraints = false
addSubview(indicatorContainer)
indicatorContainer.addSubview(indicator)
func setupIndicatorContainerConstraints() {
NSLayoutConstraint.activate([
indicatorContainer.centerXAnchor.constraint(equalTo: centerXAnchor),
indicatorContainer.centerYAnchor.constraint(equalTo: centerYAnchor),
indicatorContainer.widthAnchor.constraint(equalToConstant: frame.width / 5),
indicatorContainer.heightAnchor.constraint(equalToConstant: frame.width / 5)
])
}
func setupIndicatorViewConstraints() {
NSLayoutConstraint.activate([
indicator.centerXAnchor.constraint(equalTo: indicatorContainer.centerXAnchor),
indicator.centerYAnchor.constraint(equalTo: indicatorContainer.centerYAnchor)
])
}
setupIndicatorContainerConstraints()
setupIndicatorViewConstraints()
}
func hideIndicatorView() {
indicatorContainer.isHidden = true
indicator.stopAnimating()
indicator.isHidden = true
indicatorContainer.removeFromSuperview()
indicator.removeFromSuperview()
}
}
When I'm trying to make an instance from this class, it doesn't work in any other controller. Like this:
class SignInViewController: UIViewController {
let indicator = ActivityIndicator()
lazy var mainView: SignInView = {
let view = SignInView(delegate: self, frame: self.view.frame)
view.backgroundColor = .white
return view
}()
override func loadView() {
super.loadView()
view = mainView
}
func loginButtonTapped() {
indicator.setupIndicatorView()
}
}
I searched a lot to understand how to make it work but I haven't found a way.
You don't add it to vc's view
indicator.setupIndicatorView()
so consider
setupIndicatorView(_ view:UIView) {
.....
.....
// add it here
addSubview(indicatorContainer)
indicatorContainer.addSubview(indicator)
view.addSubview(self)
}

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

my question is about tap gesture.that is not working

class menuView
{
let View = UIView()
let resignView = UIView()
let tap = UITapGestureRecognizer()
func makeView(view:UIView){
makeResignView(view: view)
view.addSubview(View)
View.translatesAutoresizingMaskIntoConstraints = false
View.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
View.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
View.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
View.widthAnchor.constraint(equalToConstant: view.frame.width - 100).isActive = true
View.backgroundColor = UIColor.cyan
}
func makeResignView(view:UIView){
print("resing view is activate")
resignView.frame = view.frame
view.addSubview(resignView)
resignView.backgroundColor = UIColor.blue
resignView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(handleDismiss(recog:)))
resignView.addGestureRecognizer(tap)
}
#objc func handleDismiss(recog:UITapGestureRecognizer){
print("rsing view is dismiss")
View.removeFromSuperview()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.gray
}
#IBAction func PlaceView(_ sender: Any) {
let NewView = menuView()
NewView.resignView.frame = view.frame
NewView.makeResignView(view: self.view)
NewView.makeView(view: self.view)
}
}
gesture is not working.
In the menuView class i make a view and add a gesture to it .In the viewController class i add the menuView and run the code.the view is added but the gesture is not working.
The correct way should have been to inherit subview with UIView class.
See below example -
override func viewDidLoad() {
super.viewDidLoad()
let newView = subView()
newView.addGuesture()
self.view.addSubview(newView)
// Do any additional setup after loading the view, typically from a nib.
}
class subView:UIView{
func addGuesture(){
let tap = UITapGestureRecognizer()
tap.addTarget(self,action:#selector(handleTap))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tap)
self.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
self.backgroundColor = UIColor.red;
}
#objc func handleTap(){
print("tap is working")
}
}

Unable to Hide NavBar when Swiped

I am attempting to hide my navBar when swiped and has implemented navigationController?.hidesBarsOnSwipe = true at both viewWillAppear() and viewDidLoad() but the navBar remains unhidden. In my case, I have implemented a custom segmentedController below the navBar which toggles between two different tableViewControllers.
I am not sure if this is the reason why the navBar doesn't hide. My app looks like this, and the portion I want to hide is the 'Tickets' portion.
My code as such:
class TicketsViewController: UIViewController {
var upcomingTableViewController: UpcomingTableViewController!
var pastTransactionTableViewController: PastTransactionsTableViewController!
let segmentedControllerView: SegmentedController = {
let sc = SegmentedController()
sc.translatesAutoresizingMaskIntoConstraints = false
sc.segmentedController.addTarget(self, action: #selector(segmentedControlValueChanged), for: .valueChanged)
sc.segmentedController.selectedSegmentIndex = 0
return sc
}()
let containerView: UIView = {
let v = UIView()
v.translatesAutoresizingMaskIntoConstraints = false
return v
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.hidesBarsOnSwipe = true
}
override func viewDidLoad() {
super.viewDidLoad()
//These are the two tableViewControllers that are being toggled
upcomingTableViewController = UpcomingTableViewController()
pastTransactionTableViewController = PastTransactionsTableViewController()
setupNavigationBar()
setupViews()
}
#objc func segmentedControlValueChanged(_ sender: UISegmentedControl) {
let segmentedControl = sender
if segmentedControl.selectedSegmentIndex == 0 {
configureChildViewController(childController: upcomingTableViewController, onView: containerView)
} else {
configureChildViewController(childController: pastTransactionTableViewController, onView: containerView)
}
}
func setupNavigationBar() {
Helper.sharedInstance.setupNavigationBar(title: "Tickets", homeVC: self)
navigationController?.navigationBar.isTranslucent = false
navigationController?.navigationBar.shadowImage = UIImage()
navigationController?.hidesBarsOnSwipe = true
navigationItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
}
func setupViews() {
view.addSubview(segmentedControllerView)
view.addSubview(containerView)
segmentedControllerView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
segmentedControllerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
segmentedControllerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
segmentedControllerView.heightAnchor.constraint(equalToConstant: 44).isActive = true
containerView.topAnchor.constraint(equalTo: segmentedControllerView.bottomAnchor, constant: 0).isActive = true
containerView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
containerView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
configureChildViewController(childController: upcomingTableViewController, onView: containerView)
}
func configureChildViewController(childController: UIViewController, onView: UIView?) {
var holderView = UIView()
if let onView = onView {
holderView = onView
} else {
holderView = self.view
}
addChildViewController(childController)
holderView.addSubview(childController.view)
constraintViewEqual(to: holderView, childControllerView: childController.view)
childController.didMove(toParentViewController: self)
}
func constraintViewEqual(to containerView: UIView, childControllerView: UIView) {
childControllerView.translatesAutoresizingMaskIntoConstraints = false
childControllerView.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
childControllerView.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
childControllerView.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
childControllerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
}
}
The above code is my complete code for this ticketsViewController. Appreciate some advice why is the hideBarsWhenSwipe isn't hiding my navBar. Thanks.
Try resizing the element you have below to match the view controller height.

Resources