CollectionView within a CollectionView: Using didSelectItemAt to performSegue - swift

I have a collectionView within a collectionView and I want to be able to select a cell in the 2nd collectionView to perform a segue to another ViewController.
Currently, when I select a cell, I get the following message:
" Receiver ... has no segue with identifier 'ToVC2'. "
However, I have used this segue/identifier from other UIButtons and it works.
I have two ViewControllers: ViewController1 and ViewController2.
On ViewController1, there is a collectionView ("categoryCollectionView") which has vertical scrolling.
Within categoryCollectionView, there is another collectionView ("eventCollectionView") which allows horizontal scrolling.
The two collectionViews are set up and working correctly for numberOfItemsInSection and cellForItemAt. I now want to be able to select a cell within eventCollectionView, and cause a segue from ViewController1 to ViewController2.
I have added a function in ViewController1:
func segueToViewController2(event: Event){
performSegue(withIdentifier: "ToVC2", sender: event)
}
Within eventCollectionView's didSelectItemAt, I have tried the following:
var viewController1: ViewController1? = ViewController1()
viewController1.segueToViewController2(event: eventSelected)
When I select a cell, I get the following error message:
'Receiver () has no segue with identifier 'ToVC2''
However, this function performs the segue correctly if called from a regular UIButton on ViewController1 (therefore I know the issue is not that there is no segue / the identifier is wrong.) I believe the issue is that the function is being called from a collectionView within a collectionView.
Please help!!!!!

I believe the problem is that when you call var viewController1: ViewController1? = ViewController1(), you are initializing a new instance of ViewController1. This is probably not what you are meaning to do. Based on what you have said, you should pass a reference down the hierarchy of collection views you have created so that your didSelect can call the segue function on the original instance of ViewController1. Ideally, you would use a design pattern like delegation to do this.

Related

How could I keep keyboard up when tableview cells are tapped?

I am a beginner learning Swift and trying to build a search page with Swift. In my search page of the app, I have added two Views in my storyboard with one View above the other.
The upper View contains a Collection View where I have two prototypes of collection view cells. The first type of the cells has Label. The second type of the cells has TextField.
The other View on the bottom half of the screen contains a dynamic Table View where I have a list of items that can be selected. Each row of the table view cells has a selection item.
So, when I tap on a table view cell, the selection item will appear in the collection view. If I type a keyword in the TextField in the collection view, table view reloads and shows all the selection items that has the keyword, so I can tap and add an item to the collection view.
I would like to keep adding by typing a keyword after I tap on a searched item in the table view. So, I made the first cell showing selected items with labels and the second cell that has the TextField separated into two sections of the collection view. So, I only reload the first section (without TextField) for each selection. But somehow the keyboard automatically resign whenever I tap on the table view cell to add an item to the collection view.
Is there any way I can keep the keyboard up even when I tap on the tableview cells?
The keyboard also resigns when I tap the collection view cells.
I would appreciate your advice. Thanks.
I hope you are having a good day.
You can try calling this method on the UITextField you would like to show the keyboard for (maybe call it after the user taps on the UITableViewCell):
textField.becomeFirstResponder()
where "textField" is the variable name of your UITextField.
Please let me know if this fixed your issue.
Edit #1
Hello! Since my previous solution did not achieve your intended behavior. There is another solution in my mind, however I have not tried it before.
As an introduction to the concept of delegation, there is a method created by Apple called "textFieldShouldEndEditing" which is called by Apple whenever any keyboard will disappear on any text field.
This method is created by Apple, but you can override it (i.e. customize it) to suit your needs and tailor its behavior.
To override this method you have to assign your class as the delegate of UITextField by adding UITextFieldDelegate to your class definition as follows:
class YourClassName: UIViewController, UITextFieldDelegate { }
Now you have to set your class as the delegate by saying textField.delegate = self For every UITextField you create in your collection views
You then can re-create the method we discussed earlier in your class:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
//let's implement it the next steps, but for now, let's return true.
return true
}
Now instead of Apple calling their version of the method, they will call yours.
You then can create a variable in the top level of your class (I will let you know where this will be helpful later), and "maybe" name it as:
var isCellBeingClicked = false
Now upon clicking on a cell, make this variable true, I believe you are using the method didSelectRowAt (but you could be using any other method which is fine):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
[...]
isCellBeingClicked = true
[...]
}
Now back to our customized method textFieldShouldEndEditing mentioned in step 3. You can add this implementation:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
//If a cell is being clicked right now, please do not dismiss the keyboard.
if isCellBeingClicked {
isCellBeingClicked = false //reset the value otherwise the keyboard will always be there
return false
}
else { return true }
}
Please let me know if this fixes your issue.
Best regards

Programmatically press back button for UIViewController with UITableView iOS swift

I have a UIViewController that implements UITableViewDelegate, UITableViewDataSource and that contains a UITableView as a member variable. When a user click on one of the rows of that table, the app performs a storyboard segue to open the detail view controller. That detail view controller of course has a button in the top left of the screen that is the "back" button to go back up to the UIViewController with the UIViewTable.
So, suppose that I want to programmatically "click" that back button. How exactly would I do that in swift? This is the most recent version of swift (swift 4?) in XCode 10.1.
UPDATE:
So here is how I solved this. As the answers below show, it is possible to use self.navigationController?.popViewController(animated: true) to just return to the previous view controller. What I discovered I also wanted to do, however, was to call a specific method in that view controller so that it executed a certain behavior once it got shown. It turns out that is also possible, but in my case it was a bit tricky, since that prior view controller was actually a UITabBarController. Therefore I had to get the ViewController that I was interested in from the UITabBarController. I did it like this:
let numvc = navigationController!.viewControllers.count
let tvc:UITabBarController = navigationController!.viewControllers[numvc-2] as! UITabBarController
let my_vc: MyCustomVC = tvc.viewControllers![0] as! MyCustomVC
my_vc.some_function()
Here of course MyCustomV is my custom view controller class and some_function() is the method I want to call on that class. Hope this helps someone.
When You run a segue you perform a "pushViewController" method to the next view, so if you want to go back to the previous view programmatically you just have to do is pop the last view like so:
self.navigationController?.popViewController(animated: true)
UPDATE
You just need the if statement if you have multiple segues from that viewController, if not, you can delete and just cast the next view as you wish and set the properties, let the autocomplete write the *prepare(for segue... * method for you, so You don't run into any problems
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueName" {
let destinationVC = segue.destination as! CustomViewController
destinationVC.labelExample.text = "Some text I'm sending"
}
}
Are you sure you need to "click" the button?
If all you need is to dismiss details view controller, you can just call navigationController?.popViewController(animated: true)
Or if you want to deal directly with button, you can tell it to send its actions: backButton.sendActions(for: .touchUpInside)
Or if you absolutely need to show button clicking animation, then you will need something like this (you should play and choose suitable delay):
backButton.isHighlighted = true
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) {
backButton.isHighlighted = false
backButton.sendActions(for: .touchUpInside)
}

UISplitViewController - Lost reference to detail view controller on resize

I have seen plenty of questions similar to this - but not quite the same..
So I have a UISplitViewController, and the detail view controller has a lot of heavy drawing in it, so I don't want to reinit it every time I select a new row on the master view. So I observe when the user selects a row, and only perform a segue if it doesn't see a detail view controller.
Here's where the problem lies... On the iPad, if I resize the view via multi-tasking, eventually the UISplitViewController only shows the master. But when I select a row, it thinks the detail view controller is nil and allocates a new detail view controller (performs the segue). The thing is that the detail view controller still exists, It just doesn't show up on the split view controller's childViewControllers. I want to perform a segue when in the compact size, but I don't want it to recreate a new view - I want it to use the view that the split view controller is holding.
Sorry if this seems confusing.
Thanks
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// try to get my custom detail view controller
if let myVC = splitViewController?.detailViewController?.childViewControllers.first as? MyViewController {
let selectedRow = indexPath.row
myVC.doSomething(selectedRow)
} else {
// perform segue if it's not available
performSegue(withIdentifier: "showMyVC", sender: self)
}
}

Checking approach: Managing UIPageViewController page data using UITableView cells

I've implemented a UIPageViewController that allows the user to add/remove/delete new "pages" via a separate UITableView, similar to how Apple implements Cities/Weather Locations in the iPhone Weather app. Project is posted below. There is no weather or fancy UI in it at this point – I'm just trying to focus on the UIPageViewController management below. Hopefully this is useful if anyone wondering how to implement UIPageViewController with pages managed via UITableView:
https://github.com/gallaugher/PageViewControllerDemo
It seems to work fine, but I'm new to this & quite uncertain if I've done this using recommended approaches or if it's "Swifty".
Right now I have:
PageViewController [Initial View Controller]
- Sets delegate & data source to self
- Instantiates first UIViewController (imagine a CityViewController for local weather, even though details & UI not added in this example) & sets initial values.
- Cities (weather locations) are kept in a [String] array: citiesArray
In CityViewController - when "Cities" button is clicked in lower-right (created in interface builder), like Apple Weather, it opens a UITableView (CityListViewController).
- To get to this CityListViewController, I trigger a segue drawn directly via the interface builder from the "Cities" button in CityViewController to the CityListViewController, presenting modally.
- preapareForSegue passes citiesArray to the destination CityListViewController (UITableView).
In CityListViewController (the UITableView)
- User can add cities, move, delete in UITableView updating tableView & array
- Clicking a tableView row (a city's name or "Local Weather") triggers a perform segue, unwinding to CityViewController, getting source CityListViewController and using this to pass data back to the CityViewController (e.g. citiesArray = controller.citiesArray).
- This #IBAction unwind function calls an unwind to the PageViewController (really does nothing more than pass data through from the TableView in CityListViewController to the UIPageViewController PageViewController).
Unwind in PageViewController
- Grabs source (as CityViewController)
- Passes key data back (e.g. citiesArray = controller.citiesArray)
- Calls a function to instantiate view controller for the current page & set PageControl index, etc.
Q1:
While this seems to work & I haven't managed to break it during testing, is it a sound approach to go from UITableView, unwinding to a ViewController that simply triggers another unwind to the UIPageViewController, with nothing done other than pass data through?
Q2:
I've implemented the UIPageControl by building it programmatically in the PageViewController, but the button that segues to the CityListViewController (the UITableView) was created in the CityViewController using Interface Builder. Is this the proper approach? I couldn't seem to get both of these created within the same VC.
Thanks so much for those who had the patience to wade through this convoluted explanation. Still trying to get a handle on data passing among VCs, and how this relates to PageControllers & the TableViews.
For this interested in the solution I've posted to GitHub at:
https://github.com/gallaugher/PageViewControllerDemo
I've posted with more generic names - PageVewController, DetailViewController, and ListViewController, so this is easier to reuse and perhaps follow.
Thanks to Sazan Dauti for answering this question outside of StackOverflow. It is possible to segue directly between the PageController & the ListController that contains a TableView for managing pages an array of similar pages (e.g. like Apple does in the Weather app where you can add/delete/move cities), keeping the Detail (Cities in original example) free of any knowledge that it's in a PageView.
- Drag a segue directly from the UIPageViewController to the UIViewController with the TableView that contains, in my case, a list of locations (not the detail that's managed by the PageView). The segue should present modally. Key variables should be passed to the ListViewController in a prepare for segue in the PageViewController like this:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToListViewController" {
let controller = segue.destination as! ListViewController
controller.listArray = listArray
controller.currentPage = currentPage
}
}
Be sure the values passed (listArray & currentPage in the case above) are declared in ListViewController.
Add an Unwind method to the PageViewController similar to this:
#IBAction func unwindFromListViewController(sender: UIStoryboardSegue) {
pageControl.numberOfPages = listArray.count
pageControl.currentPage = currentPage
setViewControllers([createDetailViewController(currentPage)], direction: .forward, animated: false, completion: nil)
}
setViewControllers above refers to the function written in the PageViewController that contains the logic to instantiate the DetailViewController for the current page.
Return segue is created by dragging from a TableViewCell in the ListView to the "Exit' button (far right of the three buttons at the top/title view of this UIViewController). You'll be asked the name for an unwind method that should be in the PageViewController (in the example above I've called it unwindFromListViewController), so be sure to add this method before trying to make the segue.
Add a prepare for segue function to the ListViewController:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ToPageViewController" {
let controller = segue.destination as! PageViewController
controller.currentPage = currentPage
controller.listArray = listArray
}
}
and in tableView didSelectRowAt, trigger the perform segue:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
currentPage = indexPath.row // set current page to row selected
performSegue(withIdentifier: "ToPageViewController", sender: self)
}
I couldn't find any decent examples online demonstrating how this was done, so hopefully this is understandable and efficient. Any corrections are welcome. Cheers!

Pass selected row title back to main table view controller

I have a main table view controller with static cells that have a title and then a detail label. When the user clicks on "cell 1" they are taken to a second table view controller with static cells. They choose from a list ( only one cell can be picked) and then when they hit the back button whatever cells title they picked is transferred into the detail label of the cell they clicked on. The main view controller has outlets to each detail label. I've messed with it for two days and can't seem to get it to work
Add a delegate property that is type of DismissDelegate to your second view controller and set it to the first controller.
Add this protocol
protocol DismissDelegate {
func selectedCell(index : int)
}
When the second view is dismissed e.g. in the viewWillDisapper method, call the selectedCell method and pass it the selectedCell
delegate.selectedCell(self.tableView.indexOfSelectedCell) //i don't know the correct function
Then in the main view controller conform to the protocol and do whatever you need to do with the index
func selectedCell(index : int) {
//whatever you need to do with the index
}
There are several ways of solving this problem.
Using Delegate
You just implement a delegate and get the data back from the secondary view controller.
In the secondary view controller, you can call a delegate like this.
self.delegate.dataInputed(xxx)
and the first view controller's dataInputed method will be called.
In the dataInputed Method, you can update the value of the data source for the selected indexPath, and reload the Data
Send Notification
You just keep the selected table cell indexPath, and after receiving a notification from the secondary detail view controller, you can update the value of the data source for the selected indexPath, and reload the table.