In my iOS 8 app I've got a custom ViewController which I present as Popover. This ViewController has a delegate, which get, and send to parent ViewController, the clicked index in popup. The problem is that I can't dismiss this Popover after selectRow.
Here's the code:
This is the method which I call when I want to show my Popup.
#IBAction func registerButtonAction(sender: UIButton) {
popup = self.storyboard!.instantiateViewControllerWithIdentifier("PopupViewController") as? PopupViewController
popup!.modalPresentationStyle = .Popover
popup!.preferredContentSize = CGSizeMake(100, 120)
let popoverMenuViewController = popup!.popoverPresentationController
popoverMenuViewController?.permittedArrowDirections = .Up
popoverMenuViewController?.delegate = self
popoverMenuViewController?.sourceView = sender
popoverMenuViewController?.sourceRect = CGRect(
x: sender.frame.size.width/2,
y: sender.frame.size.height/2,
width: 1,
height: 1)
popup!.delegate = self
presentViewController(
popup!,
animated: true,
completion: nil)
}
Here's PopupViewController code:
protocol PopupViewControllerDelegate
{
func rowClickedAtIndex(var index : Int)
}
class PopupViewController: MainViewController {
var delegate : PopupViewControllerDelegate?
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension PopupViewController:UITableViewDelegate{
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
var cell:PopupTableViewCell? = tableView.dequeueReusableCellWithIdentifier("PopupTableViewCell") as? PopupTableViewCell
if cell == nil {
cell = PopupTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "PopupTableViewCell")
}
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (self.delegate != nil)
{
self.delegate!.rowClickedAtIndex(indexPath.row)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int{
return 2
}
}
Thank you.
I solved by myself:
I checked that the problem wasn't that the popover wasn't dismissed, but it was dismissed but after different seconds.
So I put my dismiss call in the main thread and it worked perfectly. Here's the code:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dispatch_async(dispatch_get_main_queue(),{
self.dismissViewControllerAnimated(true, completion: nil)
println(index)
})
}
}
I want to thank you Frankie, who helped me in finding the solution, removing what shouldn't be the problem.
Move your dismissViewControllerAnimated(true, completion: nil) call into the delegate at the end of the clickedRowAtIndex method. In other words, the presenting view controller should call the dismissing, not the presented view controller.
Try this:
extension WelcomeViewController: PopupViewControllerDelegate {
func rowClickedAtIndex(index: Int) {
dismissViewControllerAnimated(true, completion: nil)
println(index)
}
}
Related
I'm trying to have the following animation in my custom UITableViewCell:
When a user checks off a task, the progressBar should go from 0.0 to 1.0 in 5 seconds
What I've tried
Doing the animation in the delegate method in the TableVC:
let cell = tableView.cellForRow(at: IndexPath(item: indexSection!, section: indexRow!)) as? TaskCell
cell?.progressBar.setProgress(1.0, animated: true)
This doesn't work because it seems like the cell doesn't exist (print(cell!) gives a fatal error)
Doing the animation in TaskCell.swift
#IBAction func checkBoxAction(_ sender: Any) {
if items![indexRow!].checked {
delegate?.changeButton(state: false, indexSection: indexSection!, indexRow: indexRow!, itemID: itemID)
UIView.self.animate(withDuration: 1.0) {
self.progressBar.setProgress(0.0, animated: true)
}
} else {
delegate?.changeButton(state: true, indexSection: indexSection!, indexRow: indexRow!, itemID: itemID)
UITableViewCell.animate(withDuration: 5.0) {
self.progressBar.setProgress(1.0, animated: true)
}
}
}
This does set the progress bar, but it doesn't animate it. The progress bar abruptly changes. This is what happens
Could anyone shine a light on what I'm doing wrong? Am I calling the animation function incorrectly or am I doing it in the wrong place?
Thanks,
Matt
I have just created a simplified example and for me the following code works:
CustomTableViewCell
class CustomTableViewCell: UITableViewCell
#IBOutlet weak var progressView: UIProgressView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
#IBAction func startButtonDidTap(_ sender: UIButton) {
UIView.animate(withDuration: 2) {
self.progressView.setProgress(0.5, animated: true)
}
}
}
ViewController
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "reuseID", for: indexPath) as? CustomTableViewCell else { return UITableViewCell() }
return cell
}
}
Of course you need a proper storyboard configuration in order to run my code...
I have two controllers and I'd like to pass datas between them:
This is the first controller, a tableviewcontroller.
class BooksVC: UITableViewController {
var books: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Books"
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return books.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = books[indexPath.row]
return cell
}
}
And this is the second controller, a viewcontroller
class AddController: UIViewController {
#IBOutlet weak var inputField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func done(_ sender: Any) {
let myVC = storyboard?.instantiateViewController(withIdentifier: "BooksVC") as! BooksVC
myVC.books.append(inputField.text!)
myVC.tableView.reloadData()
dismiss(animated: true, completion: nil)
}
}
Reloaddata doesn't work, can you help me I would be very glad.
You need to have a reference to the first view controller to do what you need.
You can do something like this:
class AddController: UIViewController {
....
weak var booksVC: BooksVC?
#IBAction func done(_ sender: Any) {
booksVC.books.append(inputField.text!)
booksVC.tableView.reloadData()
dismiss(animated: true, completion: nil)
}
}
And when you instatiate an AddController, you need to pass a reference of your BooksVC. Something like this:
addController.booksVC = self
Alright for second ViewController when doneButton pressed you can
dissmisstheview
trasffer data
self.parentviewController. books.apped(inputField.text!)
self.dissmissviewcontroller
than in the first VC you can do something like this
in viewWillAppear func{
tableview.reloadData()
}
Expected:
When a UIButton is tapped, show a viewcontroller modally that has a search controller and tableview with results.
When tapping on an item in the list, change the text of the search bar to what was tapped and dismiss the viewcontroller back to the original with the UIButton now set to that text.
Actual:
UIButton calls a segue to the searchViewController.
searchViewController shows, and configures the searchController and tableView correctly.
Tapping on a cell calls the exit segue that unwinds to the original screen and correctly updates the text in the UIButton and searchbar...
but, a freaking white screen lags on the unwind segue and its driving me crazy.
Mitigation tried:
Resigning the searchController then calling the segue
programmatically
Calling self.dismiss(animated: true completion:nil) in didSelectRowAt
Putting the dismiss on the main thread with: DispatchQueue.main.async { }
calling self.presentingViewController?.dismiss(animated: true)
Video Demo of flashing
Code:
SearchDetailsViewController - the Viewcontroller to unwind to
import UIKit
class SearchDetailsViewController: UIViewController {
#IBOutlet weak var destinationButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if searchDestination != "" {
destinationButton.setTitle(searchDestination, for: UIControlState.normal)
destinationButton.setTitleColor(UIColor.black, for: UIControlState.normal)
} else {
destinationButton.setTitle("Search Nearby", for: UIControlState.normal)
}
}
#IBAction func unwindToSearchDetailsViewController(segue: UIStoryboardSegue){
}
}
SearchViewController - the problem child. I currently have the tableview cell as the exit segue in storyboard.
class SearchViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate {
#IBOutlet weak var searchResultsTableView: UITableView!
var destinationsObj:[String:[String]] = [:]
var destinations:[String] = []
var defaultDestinations:[String] = ["Search Nearby"]
var filteredDestinations:[String] = ["Search Nearby"]
var shouldShowSearchResults = false
var searchActive:Bool = false
var searchController: UISearchController!
override func viewDidLoad() {
super.viewDidLoad()
defaultDestinations = recentSearches
configureTableView()
configureSearchController()
}
override func viewDidAppear(_ animated: Bool) {
// Show search bar keyboard
searchController.isActive = true
DispatchQueue.main.async {
self.searchController.searchBar.becomeFirstResponder()
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: Configure Functions
func configureSearchController() {
searchController = UISearchController(searchResultsController: nil) //nil lets the view controller also be the search results
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Where to?"
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
searchResultsTableView.tableHeaderView = searchController.searchBar
searchController.delegate = self
}
func configureTableView() {
searchResultsTableView.delegate = self
searchResultsTableView.dataSource = self
//searchResultsTableView.isMultipleTouchEnabled = false
}
// MARK: TableView Delegate Functions
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowSearchResults {
return filteredDestinations.count
} else {
return defaultDestinations.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "idCell", for: indexPath)
if shouldShowSearchResults {
cell.textLabel?.text = filteredDestinations[indexPath.row]
} else {
cell.textLabel?.text = defaultDestinations[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40.0
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let value = tableView.cellForRow(at: indexPath)?.textLabel?.text {
self.searchController.searchBar.text = value
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
//self.searchController.resignFirstResponder()
// tableView.deselectRow(at: indexPath, animated: false)
// DispatchQueue.main.async {
// self.dismiss(animated: true, completion: nil)
// }
// self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
// MARK: UISearchBar Delegate Functions
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if let value = searchBar.text {
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
//self.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
shouldShowSearchResults = true
if searchText.characters.count > 1 {
return
} else {
if let firstLetter = searchText.characters.first{
print("Typed \(firstLetter)")
getPredictionData(firstLetter:firstLetter.description)
}
}
}
func dismissCurrentView() {
// self.presentingViewController?.dismiss(animated: true, completion: nil)
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
Screenshot of my storyboard
Well I thought I post the answer incase this happens to anyone else.
In ViewDidAppear, I was setting the searchController to active
searchController.isActive = true
Well before I dismissed, I needed to set it to inactive!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let value = tableView.cellForRow(at: indexPath)?.textLabel?.text {
self.searchController.searchBar.text = value
searchDestination = value
if !recentSearches.contains(value) {
recentSearches.append(value)
}
}
self.searchController.isActive = false
self.performSegue(withIdentifier: "cancelSearchSegue", sender: self)
}
Don't perform segue, try to dismiss view controller directly and before dismissing set the property of presenting view controller to the search result
I have a navigationController and I have added a custom UIBarButtonItem. What I want to do is when the user tap my button , it present a viewController which has a tableView inside it.
For that ,I have written this :
let shopoingCarVC:shopingCartProductsList = shopingCartProductsList()
self.navigationController?.pushViewController(shopoingCarVC, animated: true)
shopingCartProductsList is my ViewController I intended to navigate to and when It navigate to that It gave me unexpected found nil error at this line :
tableViewProducts.dataSource = self
I have done it before on my other viewControllers but I got this problem when navigating without segue and with using pushViewController mehod.
This is my targetViewCOntroller :
import UIKit
import Alamofire
import Haneke
class shopingCartProductsList: BaseViewController ,UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var ShopingCartProducts: UITableView!
var products = dataService.instance.shopingCartProduct
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
}
override func viewDidLoad() {
super.viewDidLoad()
ShopingCartProducts.dataSource = self
ShopingCartProducts.delegate = self
self.view.backgroundColor = COLOR_BACKGROUND
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let tblCell = tableView.dequeueReusableCellWithIdentifier("shopingCart_cell") as? shopingCart_cell {
if let prod = products[indexPath.row] as? productMD{
tblCell.configCell(prod)
}
return tblCell
}else{
return shopingCart_cell()
}
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("products count \(products.count)")
return products.count
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
}
What's wrong ?
Use are wrong while pushing viewcontroller user below code.
let secondViewController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as LoginViewController self.navigationController?.pushViewController(secondViewController, animated: true)
Set identifier in Identity inspector.
So i have created an app where users create custom workouts.
You enter the first view add a x number of rounds,than you click on a button in the tableview where you add rounds.That click opens another activity with a collectionview.
So the problem is here when i click on an element witch represents the exercise that needs to be in the round it sends me an empty result.By default if i add a value to that string it works.
I have noticed that the unwind happens before my didSelectItemAtIndexPath
import UIKit
class InsertWorkout: UIViewController{
#IBOutlet var InsertRoundTable: UITableView!
#IBOutlet weak var txtName: UITextField!
var RoundNumber : NSMutableArray = ["Round 1"]
var RoundLabel : NSMutableArray = [""]
var RoundExercise : NSMutableArray = ["add-1"]
var RoundExerciseImages : NSMutableArray = ["providno"]
var RoundNumber_Count=1
var RoundNumber_Add_Counter=1
var studentData : StudentInfo!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: UIButton Action methods
#IBAction func btnBackClicked(sender: AnyObject)
{
self.navigationController?.popViewControllerAnimated(true)
}
#IBAction func btnSaveClicked(sender: AnyObject)
{
if(txtName.text == "")
{
Util.invokeAlertMethod("", strBody: "Please enter workout name.", delegate: nil)
}
else
{
let studentInfo: StudentInfo = StudentInfo()
studentInfo.workout_name = txtName.text!
studentInfo.workout_benefit_1=" CUSTOM "
studentInfo.workout_benefit_2=" WORKOUT "
studentInfo.workout_benefit_3=""
studentInfo.workout_requiremnets="arsutech.com"
studentInfo.workout_time="4min"
let isInserted = ModelManager.getInstance().addWorkoutData(studentInfo)
if isInserted {
Util.invokeAlertMethod("", strBody: "Workout added", delegate: nil)
} else {
Util.invokeAlertMethod("", strBody: "Error while adding workout.", delegate: nil)
}
self.navigationController?.popViewControllerAnimated(true)
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
self.view.endEditing(true)
InsertRoundTable.endEditing(true)
}
//UITableView
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return RoundNumber.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
var cell : InsertRoundCell! = tableView.dequeueReusableCellWithIdentifier("InsertRoundCell") as! InsertRoundCell
if(cell == nil)
{
cell = NSBundle.mainBundle().loadNibNamed("InsertRoundCell", owner: self, options: nil)[0] as! InsertRoundCell;
}
let exerviseName = RoundNumber[indexPath.row]
let exerciseLabels = RoundLabel[indexPath.row]
let exerciseImage = RoundExercise[indexPath.row]
let exerciseImage_Holder = RoundExerciseImages[indexPath.row]
cell.insert_label_Round.text = exerviseName as? String
cell.addWorkout_Label.text = exerciseLabels as? String
cell.addExercise_Holder?.image = UIImage(named: exerciseImage_Holder as! String) as UIImage?
cell.addWorkoutBut.setBackgroundImage(UIImage(named: exerciseImage as! String) as UIImage?, forState: UIControlState.Normal)
cell.addWorkoutBut.tag = indexPath.row
cell.addWorkoutBut.addTarget(self, action: "logAction:", forControlEvents: .TouchUpInside)
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell as InsertRoundCell
}
#IBAction func logAction(sender: UIButton){
//self.RoundNumber.replaceObjectAtIndex(sender.tag, withObject: "Ezel")
//let titleString = self.RoundNumber[sender.tag] as? String
//Util.invokeAlertMethod("", strBody: titleString!, delegate: nil)
self.InsertRoundTable.reloadData()
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let destinationVC = segue.destinationViewController as! ChoseExercise
destinationVC.id=sender.tag
RoundNumber_Count=sender.tag
}
#IBAction func unwinndChoseExercise(segue:UIStoryboardSegue){
if let svc = segue.sourceViewController as? ChoseExercise{
self.RoundLabel.replaceObjectAtIndex(RoundNumber_Count, withObject: "\(svc.exercise_label)")
self.RoundExercise.replaceObjectAtIndex(RoundNumber_Count, withObject: "providno")
self.RoundExerciseImages.replaceObjectAtIndex(RoundNumber_Count, withObject: "\(svc.exercise_image)")
self.InsertRoundTable.reloadData()
}
}
//Add Round
#IBAction func addRound(sender: AnyObject) {
if(RoundNumber_Add_Counter<12){
RoundNumber_Add_Counter++
self.RoundNumber.addObject("Round \(RoundNumber_Add_Counter)")
self.RoundLabel.addObject("")
self.RoundExercise.addObject("add-1")
self.RoundExerciseImages.addObject("providno")
self.InsertRoundTable.reloadData()
}else{
Util.invokeAlertMethod("", strBody: "This is the maximum number of rounds", delegate: nil)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.exercisesNames.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cellexercises_insert", forIndexPath: indexPath) as! Insert_ExerciseCollectionViewCell
cell.insert_exerciseImage?.image = self.exercisesImages[indexPath.row]
cell.insert_exerciseLabel?.text = self.exercisesNames[indexPath.row]
cell.insert_exercisesHardnessImg?.image = self.exercisesHardnessImg[indexPath.row]
return cell
}
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
exercise_label="\(exercisesNames[indexPath.row])"
exercise_image="\(exercises_Exervise_Row_Names[indexPath.row])"
dismissViewControllerAnimated(true, completion: nil)
}
Here is how unwinding works:
First, put your IBAction unwind(segue:UIStoryboardSegue) in the vc where you intend to go back (presenter vc, InsertWorkout in this case).
Second, go to storyboard and choose the vc where you'll unwind from (collection view in this case) and ctrl-drag from the button/cell that causes the unwind (collection view cell in your case) to the exit button on top of that same vc and you should see a pop-up with your IBAction unwind method name under 'Selection Segue'.
Third, implement prepareforsegue in the current vc (collection vc in this case) to pass your data back.
class InsertWorkout
{
IBAction func unwind(segue:UIStoryboardSegue) {
//use the data passed from prepare to update your ui or you can also communicate to the sender using segue.sourceVC
}
}
class CollectionVC
{
override func prepareForSegue(segue: UIStoryboadSegue, sender: AnyObject?){
if segue.identifier == "whatever you put in storyboard for segue id" {
if let inserWorkoutVC = segue.destinationViewController as? InsertWorkout {
//pass the data here but don't attempt to update your destination's view ui!!,
}
}
}
}