I have 77 buttons on one view. These 77 buttons are in a collection outlet. The buttons are wired to trigger the same segue. The segue presents a detailViewController with information passed to it from the button. I need to know what button triggered the segue so that I know what data to pass to the detail controller.
I set the tag in the viewDidLoad method:
override func viewDidLoad() {
super.viewDidLoad()
self.containerView.backgroundColor = UIColor.blackColor()
var count = 0
for item in buttonOutlets {
item.layer.cornerRadius = 2.0
item.layer.borderWidth = 2.0
item.tag = count
item.layer.borderColor = UIColor.yellowColor().CGColor
item.addTarget(self, action: Selector("handleButtonPress"), forControlEvents: UIControlEvents.TouchUpInside)
count = count + 1
println(item.tag) // prints correct tag numbers
}
self.fetchAllObjects()
}
This is my prepareForSegue:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var upcoming: itemDetail = segue.destinationViewController as! itemDetail
if (segue.identifier == "loadDetailView") {
println(buttonOutlets[1].tag) // prints correct tag number
let objectPlace = sender?.tag
upcoming.parseObject = collectionObjects[objectPlace!] as? PFObject
}
The answer lies in item.addTarget. The action selector calls handleButtonPress. My original button press handler was:
func handleButtonPress(sender: UIButton) {
self.performSegueWithIdentifier("loadDetailView", sender: self)
}
What I was missing was sender: sender:
func handleButtonPress(sender: UIButton) {
self.performSegueWithIdentifier("loadDetailView", sender: sender)
}
Then add a colon to handleButtonPress call:
item.addTarget(self, action: Selector("handleButtonPress:"), forControlEvents: UIControlEvents.TouchUpInside)
Related
I have a Child UICollectionViewController where I have an array of images.
When I delete any photo I want to send back that array of updated images to Parent UIViewController.
Also in Child controller I have a programatically view which is called when I click on any image to expand it. When the image is expanded the user can click on a Delete button to delete photos from that array.
My array is updated correctly after delete but I can't manage to send it back to parent for some reasons.
I tried to send it back using Delegates and Protocols.
Here is my code for child controller:
protocol ListImagesDelegate {
func receiveImagesUpdated(data: [String]?)
}
class ListImagesVC: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
// Properties
var receivedImagesPath: [String]? = []
var fullscreenImageView = UIImageView()
var indexOfSelectedImage = 0
var imagesAfterDelete: [String]? = []
var delegate: ListImagesDefectDelegate?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("imagesAfterDelete: \(imagesAfterDelete ?? [])") // I'm getting the right number of images in this array.
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
}
...
...
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
print("Click on photo: \(indexPath.item + 1)")
if let imagePath = receivedImagesPath?[indexPath.item] {
guard let selectedImage = loadImageFromDiskWith(fileName: imagePath) else {return}
setupFullscreenImageView(image: selectedImage)
indexOfSelectedImage = indexPath.item
}
}
private func setupFullscreenImageView(image: UIImage){
fullscreenImageView = UIImageView(image: image)
fullscreenImageView.frame = UIScreen.main.bounds
fullscreenImageView.backgroundColor = .black
fullscreenImageView.contentMode = .scaleAspectFit
fullscreenImageView.isUserInteractionEnabled = true
self.view.addSubview(fullscreenImageView)
self.navigationController?.isNavigationBarHidden = true
self.tabBarController?.tabBar.isHidden = true
let deleteButton = UIButton(frame: CGRect(x: fullscreenImageView.bounds.maxX - 50, y: fullscreenImageView.bounds.maxY - 75, width: 30, height: 40))
deleteButton.autoresizingMask = [.flexibleLeftMargin, .flexibleBottomMargin]
deleteButton.backgroundColor = .black
deleteButton.setImage(UIImage(named: "trashIcon"), for: .normal)
deleteButton.addTarget(self, action: #selector(deleteButtonTapped), for: .touchUpInside)
fullscreenImageView.addSubview(deleteButton)
}
#objc func deleteButtonTapped(button: UIButton!) {
print("Delete button tapped")
receivedImagesPath?.remove(at: indexOfSelectedImage)
imagesAfterDelete = receivedImagesPath
collectionView.reloadData()
self.navigationController?.isNavigationBarHidden = false
self.tabBarController?.tabBar.isHidden = false
fullscreenImageView.removeFromSuperview()
}
Here is the Parent controller:
var updatedImages: [String]? = []
...
...
extension NewAlbumVC: ListImagesDelegate {
func receiveImagesUpdated(data: [String]?) {
print("New array: \(data ?? [])") // This print is never called.
updatedImages = data
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.delegate = self
}
}
}
I want to specify that my child controller have set a Storyboard ID ("ListImagesID") and also a segue identifier from parent to child ("goToImages"). Can cause this any conflict ?
Thanks if you read this.
It appears that the delegate is nil here
delegate?.receiveImagesUpdated(data: imagesAfterDelete)
For this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
to trigger you must have
self.performSegue(withIdentifier:"goToImages",sender:nil)
Edit: This
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
navigationController?.pushViewController(listImagesDefectVC, animated: true)
doesn't trigger prepareForSegue , so add
listImagesDefectV.delegate = self
So finally
Solution 1 :
#objc func tapOnImageView() {
let listImagesDefectVC = storyboard?.instantiateViewController(withIdentifier: "ListImagesDefectID") as! ListImagesDefectVC
listImagesDefectVC.receivedImagesPath = imagesPath
listImagesDefectVC.delegate = self
navigationController?.pushViewController(listImagesDefectVC, animated: true)
}
Solution 2 :
#objc func tapOnImageView() {
self.performSegue(withIdentifier:"goToImages",sender:nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToImages" {
let listImagesVC = segue.destination as! ListImagesVC
listImagesVC.receivedImagesPath = imagesPath
listImagesVC.delegate = self
}
}
My prepareForSegue method isn't passing data to the destination view controller.
var buttonsDictionary = [Int: UIButton]()
func createButtonArray() {
for item in statTitles {
let statisticButton = StatButton()
statisticButton.layer.cornerRadius = 10
statisticButton.backgroundColor = UIColor.darkGray
statisticButton.setTitle(String(item.value), for: UIControlState.normal)
statisticButton.setTitleColor(UIColor.white, for: UIControlState.normal)
statisticButton.titleLabel?.font = UIFont.systemFont(ofSize: 43)
statisticButton.titleEdgeInsets = UIEdgeInsetsMake(0, 20, 0, 0)
statisticButton.contentHorizontalAlignment = .left
statisticButton.addTarget(self, action: #selector(displayStatDetail), for: .touchUpInside)
statisticButton.buttonIndex = item.key
buttonsDictionary[item.key] = (statisticButton) // Assign value at item.key
print(statisticButton.buttonIndex)
}
}
func viewSavedStatistics() {
for button in buttonsDictionary {
statisticsView.addArrangedSubview(button.value)
}
}
#objc func displayStatDetail() {
self.performSegue(withIdentifier: "StatDetailSegue", sender: UIButton())
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "StatDetailSegue" {
if let destinationVC = segue.destination as? StatDetailViewController,
let index = (sender as? StatButton)?.buttonIndex {
destinationVC.statID = index
print("Destination STATID: \(destinationVC.statID)")
}
}
}
All of the above code is written in the ViewController class.
StatButton is a custom UIButton class.
The prepare is meant pass on the buttonIndex of the tapped button, but only passes on 0 and doesn't print so I don't think it's being called.
You sender is a new instance of UIButton which doesn't have any of the information you need. Instead pass the button calling the selector.
#objc func displayStatDetail(_ sender: StatisticButton) {
self.performSegue(withIdentifier: "StatDetailSegue", sender: sender)
}
You would need to change the target selector like this in the loop.
statisticButton.addTarget(self, action: #selector(displayStatDetail(_:)), for: .touchUpInside)
You’re passing a new instance of UIButton as sender here:
self.performSegue(withIdentifier: "StatDetailSegue", sender: UIButton())
Instead you should probably have your statisticButton there. Your button target selector method can have a parameter - the button instance the user clicked on. Use it as sender.
You had a mistake in performSeguefunction you've send always a new object of UIButton, not the one you've clicked on. Here is what you should do.
statisticButton.addTarget(self, action: #selector(displayStatDetail(_ :)), for: .touchUpInside)
#objc func displayStatDetail(_ sender: UIButton) {
self.performSegue(withIdentifier: "StatDetailSegue", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "editinfo"{
let selecteditem: NSManagedObject = items[self.tableView.indexPathForSelectedRow!.row] as! NSManagedObject
let viewcon: addViewController = segue.destinationViewController as! addViewController
viewcon.usernameupdate = selecteditem.valueForKey("username") as! String
viewcon.emailupdate = selecteditem.valueForKey("email") as! String
viewcon.existeditem = selecteditem
}
i tried the following but always the segue trigered on single tap
let doubleTap = UITapGestureRecognizer(target: self, action: nil)
doubleTap.numberOfTapsRequired = 2
doubleTap.numberOfTouchesRequired = 1
cell.addGestureRecognizer(doubleTap)
Check my code to double tap, its very simple.
You can put in a sample project, for debug, maybe something else is blocking your code to work properly.
override func viewDidLoad() {
super.viewDidLoad()
let doubleTap = UITapGestureRecognizer(target: self, action: "doubleTapHandler")
doubleTap.numberOfTapsRequired = 2
view.addGestureRecognizer(doubleTap)
}
func doubleTapHandler() {
print("double tap")
}
i tried to add gesture recognizer in my UIImageView
let rc = UITapGestureRecognizer(target: self, action: "foo:")
rc.numberOfTapsRequired = 1
rc.numberOfTouchesRequired = 1
cell.bar.tag = indexPath.row
cell.bar.addGestureRecognizer(rc)
but didn't call my foo function
func foo(sender: UIImageView! ) {
self.performSegueWithIdentifier("VC", sender: sender)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "VC" {
let vc = segue.destinationViewController as! VC
vc.item = items[sender.tag]
}
}
So, as I mentioned before your problem is UIImageView by default has property userInteractionEnabled set to false. You can change this in you storyboard, or add line cell.bar.userInteractionEnabled = true.
Next your problem is in your foo: method implementation: you specify sender as UIImageView!, but it should be UITapGestureRecognizer. This is why it crashes - it cannot be UIImageView, so when it unwraps (!) it is nil.
Solution: change your foo method declaration to foo(recognizer: UITapGestureRecognizer). If you need access your imageView inside this method you can use code below:
if let imageView = recognizer.view as? UIImageView {
...
}
or with new guard keyword (Swift 2.0)
guard let imageView = recognizer.view as? UIImageView
else { return }
...
You can change
foo(recognizer: UITapGestureRecognizer) {
}
I have several buttons defined in a loop in viewDidLoad. They are inside a content view, which is in a scroll view.
for var i:Int = 0; i < colleges.count; i++ {
let collegeButton = UIButton.buttonWithType(UIButtonType.System) as UIButton
collegeButton.frame = CGRectMake(0, 0, self.contentView.frame.size.width, 30)
collegeButton.center = CGPoint(x: self.contentView.center.x, y: 30 * CGFloat(i) + 30)
collegeButton.setTitle(sortedColleges[i], forState: UIControlState.Normal)
collegeButton.setTitleColor(UIColor.darkGrayColor(), forState: UIControlState.Normal)
collegeButton.titleLabel?.font = UIFont(name: "Hiragino Kaku Gothic ProN", size: 15)
collegeButton.addTarget(self, action: "collegeSelected:", forControlEvents: UIControlEvents.TouchUpInside)
self.contentView.addSubview(collegeButton)
}
As you can see, the title of each button is set based on a previously defined array of strings. Whenever a button is selected, a segue is called to move to a table view, which is embedded in a navigation controller.
I need to set the navigation controller title to be the same as the specific button title.
Here are some things I have tried:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var destViewController:videosTableView = segue.destinationViewController as videosTableView
//destViewController.title = collegeButton.titleLabel?.text
//var buttonTitle = sender.titleLabel?.text
//destViewController.title = buttonTitle
}
The commented lines were failed attempts.
I also tried working in the action function called when the button is pressed:
func collegeSelected(sender: UIButton!) {
self.performSegueWithIdentifier("goToTableView", sender: self)
//Set navigation title of next screen to title of button
var buttonTitle = sender.titleLabel?.text
println(buttonTitle)
}
Using the sender to get the button title works, but I don't know how to pass it to the next view controller.
Thanks a lot to anyone who can help.
In your button function, pass the button along as the sender of the segue:
func collegeSelected(sender: UIButton!) {
self.performSegueWithIdentifier("goToTableView", sender: sender)
}
Then in prepareForSegue, get the button title from the sender (i.e. the button):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destViewController = segue.destinationViewController as videosTableView
if let buttonTitle = (sender as? UIButton)?.titleLabel?.text {
destViewController.title = buttonTitle
}
}