Prepare for Segue function not passing data correctly - ios

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)
}

Related

How to pass array of data between UIViewControllers from #objc function

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
}
}

let user press button only one time

I want to let user give +1 with one button or -1 points with another button, but they should be able to only press one of these buttons one time...
I use this code, but the user can still click on the button multiple times...
var job: Job! {
didSet {
jobLabel.text = job.text
likeButton.setTitle("πŸ‘ \(job.numberOfLikes)", for: [])
dislikeButton.setTitle("πŸ‘Ž \(job.numberOfDislikes)", for: [])
}
}
#IBAction func dislikeDidTouch(_ sender: AnyObject)
{
(dislikeDidTouch).isEnabled = false
job.dislike()
dislikeButton.setTitle("πŸ‘Ž \(job.numberOfDislikes)", for: [])
dislikeButton.setTitleColor(dislikeColor, for: []) }
#IBAction func likeDidTouch(_ sender: AnyObject)
{
sender.userInteractionEnabled=YES;
job.like()
likeButton.setTitle("πŸ‘ \(job.numberOfLikes)", for: [])
likeButton.setTitleColor(likeColor, for: [])
}
Since the sender is a UIButton , it's better to construct the funcs like this
#IBAction func dislikeDidTouch(_ sender: UIButton)
#IBAction func likeDidTouch(_ sender: UIButton)
and inside each one do
sender.isEnabled = false

How To Identify Which Button, Of Many, Triggered Single Storyboard Segue

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)

Swift handle action on segmented control

I have a HMSegmentedControl with 4 segments. When it is selected, it should pop up view. And when the pop up dismissed, and trying to click on same segment index it should again show the pop up. By using following does not have any action on click of same segment index after pop up dissmissed.
segmetedControl.addTarget(self, action: "segmentedControlValueChanged:", forControlEvents: UIControlEvents.ValueChanged)
You can add the same target for multiple events.
So lets say your segmentedControlValueChanged: looks like this:
#objc func segmentedControlValueChanged(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
// value for first index selected here
}
}
Then you can add targets for more than 1 events to call this function:
segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .valueChanged)
segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .touchUpInside)
Now your function will get called when a value was changed and when the user releases his finger.
with sender, use the sender name sender when you want to access in the action:
segmentControl.addTarget(self, action: #selector(changeWebView(sender:)), for: .valueChanged)
or
addTarget(self, action: #selector(changeWebView), for: .valueChanged)
Swift 5
// add viewController
#IBOutlet var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.addTarget(self, action: #selector(CommentsViewController.indexChanged(_:)), for: .valueChanged)
}
// using change
#objc func indexChanged(_ sender: UISegmentedControl) {
if segmentedControl.selectedSegmentIndex == 0 {
print("Select 0")
} else if segmentedControl.selectedSegmentIndex == 1 {
print("Select 1")
} else if segmentedControl.selectedSegmentIndex == 2 {
print("Select 2")
}
}
You set your target to fire just when the value change, so if you select the same segment the value will not change and the popover will not display, try to change the event to TouchUpInside, so it will be fired every time you touch inside the segment
segmentedControl.addTarget(self, action: #selector(segmentedControlValueChanged(_:)), for: .touchUpInside)
#IBAction func segmentedControlButtonClickAction(_ sender: UISegmentedControl) {
if sender.selectedSegmentIndex == 0 {
print("First Segment Select")
}
else {
print("Second Segment Select")
}
}
Swift4 syntax :
segmentedControl.addTarget(self, action: "segmentedControlValueChanged:", for:.touchUpInside)
Drag and drop create an action which is a value type.
#IBAction func actionSegment(_ sender: Any) {
let segemnt = sender as! UISegmentedControl
print(segemnt.selectedSegmentIndex)// 0 , 1
}

How to pass button title to next view controller in Swift

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
}
}

Resources