Segue using wrong animation and back button not showing up [closed] - iphone

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I am using a UINavigationController inside a UITabBarController in my project.
Inside this UINavigationController there is an UICollectionView. In the UICollectionView, after tapping on an item in the collection a show segue should be performed to see some kind of detail page from where you can go either back or to another collection with a different data-filter applied.
Being new to iOS development, I struggle with getting the things to behave as I want. Even though all segues are set to show in the storyboard they animate as modal animation (coming in from the bottom instead of the right side).
Also the back button does not appear. I set text for it in the attribute inspector (I already know that these have to be in the view that 'goes away') and also added titles to all of the navigation items… still no sign of them showing up.
Here is my storyboard:
This is the prepareForSegue I use:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showService" {
if let indexPath = self.collectionView?.indexPathsForSelectedItems() {
let object = serviceData[indexPath.last!.row]
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! ServiceDetailViewController
controller.serviceItem = object
controller.callerItem = self.name
controller.navigationItem.leftItemsSupplementBackButton = true
}
}
}
I fire the segue in collectionView:didSelectItemAtIndexPath: of my collection view:
override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {
let cell = self.collectionView?.cellForItemAtIndexPath(indexPath)
performSegueWithIdentifier("showService", sender: cell)
}

The problem is that your segue runs from a navigation controller's child ("Another Title") to a navigation controller. That is not how you construct a push/show segue within a navigation interface. You need to run from the navigation controller's child to another child, that is, e.g. to your "Title" view controller. Thus, if (let's say) your push/show segue runs from "Another Title" to "Title", then when that segue is triggered, the "Title" view controller will be pushed onto the same navigation controller's stack as the "Another Title" view controller, and so you'll get the title change in the nav bar and the back button.

Related

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!

Warning: Attempt to present ViewController on ViewController which is already presenting ViewController

I have a view controller with a toolbar with 3 UIButtons that open a new view controller as a popover. I created the segues in Storyboard and selected "Present as Popover". The popovers work but when the user taps on another button while a popover is currently open, I get this error:
Warning: Attempt to present <Fingerpainter.OpacityViewController: 0x79095110> on <Fingerpainter.DrawingViewController: 0x7b278000> which is already presenting <Fingerpainter.BrushSizeViewController: 0x79573770>
Is there a way to like make sure all popovers are closed before opening a new one? Here's my prepareForSegue method in the main ViewController (containing the toolbar):
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let identifier = segue.identifier ?? ""
let popoverPresentationController = segue.destinationViewController.popoverPresentationController
popoverPresentationController!.delegate = self
switch identifier {
case Storyboard.BrushSizeSegueIdentifier:
if let brushSizeViewController = popoverPresentationController?.presentedViewController as? BrushSizeViewController {
// set properties in brushSizeViewController
}
case Storyboard.OpacitySegueIdentifier:
if let opacityViewController = popoverPresentationController?.presentedViewController as? OpacityViewController {
//set properties in opacityViewController
}
case Storyboard.ColorSegueIdentity:
if let colorViewController = popoverPresentationController?.presentedViewController as? ColorViewController {
//set properties in colorViewController
}
default:
break
}
}
Is there a way to like make sure all popovers are closed before opening a new one
It's the other way around. It's your job to make sure that while the popover is present, a button that summons another popover is not tappable. You can do this by disabling the button, but more commonly, in order to coordinate the disabling of the button with the presence of the popover, it's done by adjusting the popover presentation controller's passthroughViews.
Unfortunately there's a massive and long-standing bug where even setting the passthroughViews to nil doesn't prevent toolbar buttons from being tappable. The workaround is to do it with a delay. A lot of my popover code adds this sort of thing:
if let pop = popoverPresentationController {
delay(0.1) {
pop.passthroughViews = nil
}
}
(where delay is described here: https://stackoverflow.com/a/24318861/341994).

iPhone popup menu like iPad popover?

How can i implement this popup menu in iphone app like a popover in ipad?
EDIT: This is the best at moment: https://github.com/runway20/PopoverView
iOS 8 and later
Beginning with iOS 8, you can use UIPopoverPresentationController for iPhones in addition to iPads.
Setup
Add a UIBarButtonItem to your main View Controller.
Add another View Controller to the storyboard. Change it to the size that you want the popover to be and add any content that you want it to have. For my example I just added a UILabel. If you want a whole menu, then just add a table view or list of buttons.
Add a segue from the bar button item to the view controller that you will use as the popover. Rather than show, choose Present as Popover.
Select the segue in the storyboard and set the identifier to popoverSegue (or whatever string you called it in the code).
In the Attributes inspector for the popover view controller, check Use Preferred Explicit Size and confirm that it is the size you want it to be.
Code
This is the code for the main view controller that has the bar button item in it.
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "popoverSegue" {
let popoverViewController = segue.destinationViewController
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
popoverViewController.popoverPresentationController!.delegate = self
}
}
// MARK: - UIPopoverPresentationControllerDelegate method
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
// Force popover style
return UIModalPresentationStyle.None
}
}
Popover at an arbitrary anchor point
If you want to set the popover to appear somewhere besides a bar button item (on a UIButton for example) then you need to set the sourceView and sourceRect. See this answer for details.
Further reading
The above example comes mostly from the first link.
iPad Style Popovers on the iPhone with Swift
iOS 8 Popover Presentations
UIPopoverPresentationController on iOS 8 iPhone
General overview of popup options in iOS
Have a look at the iPhone UIPopoverController implementation: WEPopover
On iPhone you would generally use a UIActionSheet for a stack of buttons like that. It slides up from the bottom, rather than popping up next to the button, but that's the standard behavior on iPhone.
There is one that is even better than WEPopover. Developed by a company called 50pixels, it is called FPPopover.
You can download FPPopover at https://github.com/50pixels/FPPopover
You would have to manually instantiate a UIView using a custom background image or drawing with transparency, add some UIButtons (or other type of custom view) on top, and also somehow handle all touches outside that view.
Note that is is non-standard UI. An actionsheet would be more HIG compliant.
To get a popover from a right side bar button item on a navigation controller that is part of a tableview controller, the following worked for me for Swift 4 and Xcode 9.
Follow the steps in Suragch answer above (as edited by the Community.)
Do not implement the Segue as shown in the answer above. For some reason, the segue causes the popover to go full screen despite setting the explicit size.
Give your popover view controller a title in Attributes Inspector
Add the following code in the TableView controller where the popup will show.
Modify the string identifier (the one here is referencing a Constant.swift file)
Modify "as! FilterVC" to use the title of the your popover view controller.
/// Shows a filter popover view
#IBAction func filterBtnPressed(_ sender: UIBarButtonItem) {
let popover = storyboard?.instantiateViewController(withIdentifier: FILTER_VC) as! FilterVC
popover.modalPresentationStyle = UIModalPresentationStyle.popover
popover.popoverPresentationController?.backgroundColor = UIColor.green
popover.popoverPresentationController?.delegate = self
popover.popoverPresentationController?.backgroundColor = ColorPalette.Blue.Medium
popover.popoverPresentationController?.sourceView = self.view
popover.popoverPresentationController?.sourceRect = CGRect(x: self.view!.bounds.width, y: 0, width: 0, height: 0)
popover.popoverPresentationController?.permittedArrowDirections = .up
self.present(popover, animated: true)
} }
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.none
}
You can check WYPopoverController: https://github.com/sammcewan/WYPopoverController
The screenshot above is not a UIActionSheet. It looks like a simple UIView subclass with custom UIButtons on top of it. So go ahead and create the subclass according to your needs and then add it as a subview to your view every time you need it.