index tag UITapGestureRecognizer not responive when applied to outlet collection - swift

When a UIView is touched it should move. When the UIView is touched nothing happens. All of the UIViews are connected in a outlet collection. No constraints are applied in the storyboard. I do not understand why nothing is happening when I touch the UIViews. My code is in swift.
import UIKit
class ViewController: UIViewController {
#IBOutlet private var cardView: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
for index in cardView.indices {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapCard(sender: )))
cardView[index].isUserInteractionEnabled = true
cardView[index].addGestureRecognizer(tap)
cardView[index].tag = index
}
}
#objc func tapCard (sender: UITapGestureRecognizer) {
let clickedView = cardView[sender.view!.tag]
print("View tapped !" , clickedView )
}
}

Make sure that your iboulet collection work correctly. Try change some property of that views...
And second, does you down through that code on touch and nothing happens, or you just doesn't rich that part of code on touch? thank you

Related

Views not performing segue

I have a collection of views and I want to make that when they are tapped, it will perform the same segue. and no view performs any segue.
class ViewController: UIViewController {
#IBOutlet var categoryViews: [UIView]!
let tapGesture = UIGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))
override func viewDidLoad() {
super.viewDidLoad()
for category in (0..<categoryViews.count) {
categoryViews[category].addGestureRecognizer(tapGesture)
categoryViews[category].isUserInteractionEnabled = true
}
// Do any additional setup after loading the view.
}
#objc func move(tap: UIGestureRecognizer) {
performSegue(withIdentifier: "Animals", sender: nil)
}
}
A single instance of UITapGestureRecognizer can be added to a single view.
In your code, since you're using a single instance of UITapGestureRecognizer for each view, the tapGesture will be added only to the last view in categoryViews array.
You need to create different UITapGestureRecognizer instance for each view in categoryViews, i.e.
class ViewController: UIViewController {
#IBOutlet var categoryViews: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
categoryViews.forEach {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(move(tap:)))
$0.addGestureRecognizer(tapGesture)
$0.isUserInteractionEnabled = true
}
}
#objc func move(tap: UITapGestureRecognizer) {
performSegue(withIdentifier: "Animals", sender: nil)
}
}
The problem is that this code doesn't do what you think it does:
class ViewController: UIViewController {
let tapGesture = UIGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))
Your let tapGesture is an instance property declaration, and what follows the equal sign is its initializer. But you can't speak of self in an instance property initializer; there is no instance yet. So self here is taken to be the class. Thus, your tap gesture recognizer "works", but the move message is not sent to your ViewController instance; in effect, it is sent into empty space.
To fix this, you can initialize tapGesture at a time when self does exist. For example:
class ViewController: UIViewController {
let tapGesture : UIGestureRecognizer!
func viewDidLoad() {
self.tapGesture = UITapGestureRecognizer(target: self, action: #selector(ViewController.move(tap:)))

delegate is always nil

I have a custom UIView class called MyView and a View Controller.
When the user taps a button on the UIView, I want to call a function on the view controller. I'm trying to achieve this through delegation
custom UIClass
#objc protocol MyViewDelegate{
optional func expandCollapse()
}
class MyView: UIView, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource, UICollectionViewDelegate{
weak var delegate:MyViewDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if self.subviews.count == 0 {
loadNib()
}
}
override init(frame:CGRect){
super.init(frame: frame)
loadNib()
}
func loadNib(){
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "MyView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! MyView
view.frame = bounds
view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(view);
}
#IBAction func expandit(sender: AnyObject) {
//this is where it fails. delegate is nil
delegate!.expandCollapse!()
}
}
My View Controller
class ViewController2: UIViewController, MyViewDelegate {
#IBOutlet weak var theview: UIView!
var myview : MyView?
override func viewDidAppear(animated: Bool) {
super.viewWillAppear(animated)
myview = MyView(frame: CGRectMake(0,0,theview.frame.size.width,theview.frame.size.height))
self.theview.addSubview(myview!)
myview!.delegate = self
}
func expandCollapse() {
viewheight.constant = 172
UIView.animateWithDuration(0.5) {
self.view.layoutIfNeeded()
}
}
In the UIView, the delegate is always nil. What am I missing?
Using delegation for this is simply unsuitable. You are fighting UIKit design patterns.
The whole situation is very simple.
You have your ViewController.
Then you have your totally independent custom view.
Essentially, you want somehow to route the TouchUpInside event from the button to get to viewController.
If your Custom view contains a button, then the accessibility level of this button is internal by default. Looking at the code, I assume you created the button in Interface builder. Make an outlet from the custom view class to the button, so that there is a programatically accessible reference to it.
Your view controller declares an instance of this custom view. Then, in viewDidLoad you have to use the target-action pattern.
self.customView.button.addTarget(target: self, action: "expandCollapse", forControlEvents: .TouchUpInside)
That's basically all there is to it.
I'm not entirely confident of my ARC understanding, but I believe the issue is that your delegate is a weak reference and there's nothing keeping a reference to the delegate after it's set, so it' deallocated.
Replace it with this and I believe it will work:
var delegate:MyViewDelegate?
Try assigning the delegate to "myview" before adding it to "theview"
The problem is in the loadNib() member function.
You're creating two instances of "MyView". The second instance being added as a subview.
You're setting the delegate in one instance and referring to a nil delegate in the other instance.
Try using a static class method like below to create one instance of "MyView"
class func loadFromNib() -> MyView? {
guard let myView = Bundle.main.loadNibNamed("MyView", owner: nil, options: nil)?.first as? MyView else {
assertionFailure("Failed to load nib for 'MyView'!")
return nil
}
return myView
}
Doing it this way, you won't need the custom init()s either.
Hope that helps!

Need to free memory after Modal Segues, but I need both my Segues to pass data from A to B and B to A

I am making a game for iOS with SpriteKit.
I have 2 Viewcontrollers. One is the GameViewController and the other one is the MenuViewController. Let's call them A and B respectively.
When the player dies, a function is called in GameScene.swift that launches a modal "Lost" Segue to B. There, the player can restart the game or buy a life and a "Back" Segue is called to A.
I need to dismiss the additional Views that get created each time I call a segue.
Problem is: I need the "Lost" Segue to send data about the Score to View B and I need the "Back" Segue to send data to View A about wether or not the player used a life.
I have implemented all this. But now I need to find how to dismiss old views that keep eating the device's memory, thus leading to lag and crash.
I have googled for hours and hours. No solution was adapted to my situation.
The solutions I found either caused my app to bug, data not to be passed or views not to be generated.
I will not add code here since there is a LOT. But I am sure the answer is actually really easy, just not for a beginner like me.
I think a possible solution would be an unwind segue from B to A ?
But do unwind segues pass data along ?
Moreover, I found no answer I could understand on how to use an unwind segue.
I exhausted all my possibilities. Stack Exchange is my last chance.
You definitely should use an unwind segue to return to the previous viewController, otherwise as you have found your memory usage increases until your apps quits.
I created the following example from your description. It uses a standard segue to move from the GameViewController to the MenuViewController and it uses an unwind segue to move from the MenuViewController back to the GameViewController.
The GameViewController has a Player Dies UIButton, a UITextField for entering a score, and a UILabel for displaying the lives.
The MenuViewController has a UILabel for showing the score, a Buy a Life UIButton for adding lives, and a Restart UIButton for returning to the GameViewController.
Here's the code:
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
updateLivesLabel()
}
// This is the function that the unwind segue returns to.
// You can call it anything you want, but it has to be in
// the viewController you are returning to, it must be tagged
// with #IBAction and it must take a UIStoryboardSegue as its
// only parameter.
#IBAction func returnFromMenu(segue: UIStoryboardSegue) {
print("We're back in GameViewController")
// Update the lives label based upon the value passed in
// prepareForSegue from the MenuViewController.
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
self.performSegueWithIdentifier("Lost", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Lost" {
let destinationVC = segue.destinationViewController as! MenuViewController
destinationVC.score = Int(scoreTextField.text ?? "") ?? 0
destinationVC.lives = lives
}
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
self.performSegueWithIdentifier("Back", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Back" {
let destinationVC = segue.destinationViewController as! GameViewController
destinationVC.lives = lives
}
}
}
This is how you wire up the forward segue to be called programmatically:
Control-drag from ViewController icon to the MenuViewController:
Select Present Modally from the pop-up:
Click on the segue arrow between the viewControllers and give it an identifier in the Attributes Inspector:
This is how you wire up the unwind segue to be called programmatically:
Control-drag from ViewController icon to Exit icon:
Choose returnFromMenu from pop-up:
Click on the Unwind Segue in the Document Outline and give it the identifier "Back" in the Attributes Inspector on the right:
Alternate Answer
Instead of using segues, you can present and dismiss viewControllers manually. The advantage for your app is that the MenuViewController will be allocated only once and will persist for the life of the app. This same viewController will be presented and dismissed repeatedly, but it will not be deallocated which I suspect is leading to your crashes.
The GameViewController will be the initialViewController that is created by the Storyboard. The MenuViewController will be loaded in viewDidLoad of the GameViewController.
To make this work, you need to add an identifier to the MenuViewController so that it can be instantiated by name. Click on the MenuViewController in the Storyboard and set its Storyboard ID in the Identity Inspector:
Here is the code. Note that all mention of segues is gone. Note how viewWillAppear is used to update the viewControllers.
GameViewController.swift
import UIKit
class GameViewController: UIViewController {
#IBOutlet weak var scoreTextField: UITextField!
#IBOutlet weak var livesLabel: UILabel!
var menuViewController: MenuViewController?
var lives = 3
func updateLivesLabel() {
livesLabel.text = "Lives: \(lives)"
}
override func viewDidLoad() {
super.viewDidLoad()
menuViewController = self.storyboard!.instantiateViewControllerWithIdentifier("MenuViewController") as? MenuViewController
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
updateLivesLabel()
}
#IBAction func goPlayerDies(sender: UIButton) {
lives--
menuViewController?.score = Int(scoreTextField.text ?? "") ?? 0
menuViewController?.lives = lives
self.presentViewController(menuViewController!, animated: true, completion: nil)
}
}
MenuViewController.swift
import UIKit
class MenuViewController: UIViewController {
#IBOutlet weak var scoreLabel: UILabel!
var score = 0
var lives = 0
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
scoreLabel.text = "Score: \(score)"
}
#IBAction func buyLife(sender: UIButton) {
lives++
}
#IBAction func goRestart(sender: UIButton) {
let destinationVC = self.presentingViewController as! GameViewController
destinationVC.lives = lives
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}

Coordinates of image area tapped in swift programming

I'm new to Swift app development and I would like to know how to add touch events based on where an Image is clicked using Swift. I need to get the coordinates of the area the image is tapped.
You'll need a tap gesture recognizer on the image view, you also need to set the user interaction property to enabled. Then you can get the point from the gesture recognizer's method. Here's some quick code:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapAction:"))
self.imageView.userInteractionEnabled = true
self.imageView.addGestureRecognizer(tapGestureRecognizer)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tapAction(sender: UITapGestureRecognizer) {
let touchPoint = sender.locationInView(self.imageView) // Change to whatever view you want the point for
}
}
UPDATE 2017:
Now Selector(String) is deprecated. One can use the new syntax #selector.
Also, the colon at the end is not needed.
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapAction))

How to make keyboard dismiss when I press out of searchbar on Swift?

I try to make my searchbar on swift, but I have a problem to dismiss keyboard on screen when I pressed out of searchbar. When I try with textfield, it works perfectly fine with this code.
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true)
}
It work when i press out of my textfield and then the keyboard is gone. I want to make like that with my searchbar, because when I use searchbar and use the same way like textfield, it doesn't work at all. Any reference or code is very useful for me.
try this :
self.mySearchController.searchBar.endEditing(true)
replace mySearchController with your created controller name..
If you did not create it programmatically but instead you just dragged a search bar from library then IBoutlet your searchable to your class and reference it as:
self.mySearchBar.endEditing(true)
I found it easier and simplier to use Table View for dismissal. (If you're using table view)
Swift 4:
self.tableView.keyboardDismissMode = .onDrag
Tested and working!
func searchBarSearchButtonClicked(searchBar: UISearchBar)
{
searchActive = false;
self.mySearchBar.endEditing(true)
}
Edit for Swift 4.2
func searchBarSearchButtonClicked(_ searchBar: UISearchBar)
{
searchActive = false
self.searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(searchBar: UISearchBar) {
searchActive = false;
searchProject.resignFirstResponder()
}
This method will be invoked when user click search button on keyboard.So here we can dismiss keyboard.I think this is the right method.
Firstly, Apple's UISearchBarDelegate is the correct solution to hide keyboard when users click a search button while UISearchBar's instance is the first responder (learn UIResponder). In short, searchBarSearchButtonClicked(_:) is what you need for this task.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder() // hides the keyboard.
doThingsForSearching()
}
If it doesn't work, check, does your controller conform to UISearchBarDelegate and secondly, does UISearchBarDelegate know about your class implementation (if you don't quite understand what am I talking about, you should learn delegation pattern starting to read here):
class YourAwesomeViewController: UIViewController, UISearchBarDelegate { // pay attention here
#IBOutlet weak var yourSearchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
self.yourSearchBar.delegate = self // and it's important too
}
}
Further, if you need to hide the keyboard touching outside of search bar without touching the search button (the user may change his mind to search something), UITapGestureRecognizer is a simple way too to deal with that.
Ctrl-drag a Tap Gesture Recognizer from the Object Library to your View Controller.
Ctrl-drag the recently added Tap Gesture Recognizer from the document outline in the storyboard to your class implementation as IBAction.
Finally, write a code:
#IBAction func tapToHideKeyboard(_ sender: UITapGestureRecognizer) {
self.yourSearchBar.resignFirstResponder()
}
Also, don't forget to create #IBOutlet for the search bar to have an access inside your class implementation.
Both variants above work well in my project.
Swift 4+:
You can try, creating a tap gesture and add in the self.view
let singleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.singleTap(sender:)))
singleTapGestureRecognizer.numberOfTapsRequired = 1
singleTapGestureRecognizer.isEnabled = true
singleTapGestureRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(singleTapGestureRecognizer)
and in selector func you call self.searchBar.resignFirstResponder
#objc func singleTap(sender: UITapGestureRecognizer) {
self.searchBar.resignFirstResponder()
}
You can use a general UIViewController extension
Just add a new swift file on the project and paste the following code snippet
Code
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard(_:)))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
if let nav = self.navigationController {
nav.view.endEditing(true)
}
}
}
Now call hideKeyboardWhenTappedAround() from viewDidLoad method where you want keyboard hiding feature.
class MaCaveViewController: UIViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
}
// When button "Search" pressed
func searchBarSearchButtonClicked(_ searchBar: UISearchBar){
print("end searching --> Close Keyboard")
self.searchBar.endEditing(true)
}
}
This works very well for me.
we can do this with following methods
func searchBarTextDidBeginEditing(searchBar: UISearchBar) {
searchBar.showsCancelButton = true;
}
func searchBarTextDidEndEditing(searchBar: UISearchBar) {
searchBar.showsCancelButton = false;
}
This works for me in Swift 4
func searchBarSearchButtonClicked(_ searchBar: UISearchBar){
self.searchBar.endEditing(true)
}