Programmatic embed segue - ios

I'm using a Page View Controller to implement an image carousel in my app. I have a container view in my parent view controller which has an embed segue to the Page View Controller. The problem I'm having is when I need to go off and fetch these images asynchronously and the segue happens before viewDidLoad gets the images.
My prepare for segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "startCarousel" {
let carouselPageViewController = segue.destination as! CarouselPageViewController
carouselPageViewController.images = carouselImages
}
}
in viewDidLoad:
GalleryManager.sharedInstance.getData = {
(data) in
// Assign images to instance property here ...
}
My thinking is that once I get the images that I programmatically start the segue myself. Or if I should be doing this a different way any advice would be much appreciated.

You can do something like, return false in shouldPerformSegue and initiate segue when data is loaded
i.e.
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if identifier == "startCarousel" {
return (detail != nil)
}
return true
}
Then in viewDidLoad
GalleryManager.sharedInstance.getData = { [weak self]
(data) in
self?.performSegue(withIdentifier: "startCarousel", sender: nil)
}

No, you can't delay an embed segue. If you use an embed segue it fires as soon as the view controller is loaded.
You need to wait until the data loads and then call the setViewControllers(direction:animated:completion) to install the view controllers that you will use to display the data once the data is loaded.

Swift 3, Xcode 8
You can realize in this way:
set a reference to your page view controller in prepareForSegue in your masterViewController:
var myContainerViewController: UIPageViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier == "embedSegue" {
if let destination = segue.destination as? YourPageViewControllerClass {
self.myContainerViewController = destination
}
}
Define a function in your own pageViewController class:
func someFunction(){
//configure your image or swipe page
}
when you get the images, in your masterViewController call:
self.myContainerViewController?.someFunction()

you can't delay an embed segue.
you try to put button in view controller and then after view load set button click to set image...

Related

Having trouble "reaching" my override prepare function to programmatically change views in a tab bar controller

I have a view controller with a container view that has a tab bar controller embedded in it. Im using this to display 3 different view controllers based on what is pressed from a segmented control in the main vc. I have been following this solution: https://stackoverflow.com/a/38283669/11536234
My problem is that when I change the segmented control index (by pressing a different segment) I can't figure out how to "reach" the prepare function.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
print("prepare reached")
//super.prepare(for: segue, sender: sender)
//switch(segue.identifier ?? "") {
//case "TabBar":
guard let TabController = segue.destination as? UITabBarController else {
fatalError("Unexpected destination: \(segue.destination)")
}
TabController.selectedIndex = toggle.selectedSegmentIndex
//default:
//fatalError("Unexpected Segue Identifier; \(segue.identifier)")
//}
}
#IBAction func toggleAction(_ sender: UISegmentedControl) {
print("toggle is now at index: ", toggle.selectedSegmentIndex)
//performSegue(withIdentifier: "TabBar", sender: sender)
//container.bringSubview(toFront: views[sender.selectedSegmentIndex])
}
So far i have tried placing a performsegue function in an action function linked to the segmented control. This doesn't work, however, because it essentially adds another embedded tab bar programmatically or calls the embed segue again and I receive this error statement: "There are unexpected subviews in the container view. Perhaps the embed segue has already fired once or a subview was added programmatically?"
*The commented lines of code are there to show what I've tried that hasn't worked vs where I'm at.
When you embed a view controller to a container view from another view controller(MainVC), the segue is performed only once when MainVC loads. To pass values to the embedded UIViewController/UITabBarController, you need to get the child view controller and send the data
class MainVC: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
if let tabBarVC = self.children.first(where: { $0 is UITabBarController }) as? UITabBarController {
tabBarVC.selectedIndex = sender.selectedSegmentIndex
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
//called before mainvc viewDidLoad
let destinationVC = segue.destination as? UITabBarController
destinationVC?.selectedIndex = 1
}
}
You can't do what you are trying to do.
With embed segues the segue fires when the host view controller is first loads. That invokes your prepare(for:sender) method, once and only once, before the embedded view controller's views are loaded. It doesn't get called again.
What you need to do is to save a pointer to your child view controller in an instance variable. Define a protocol that the parent uses to talk to the child, and then use that protocol to send a message to the child when the user selects a different segment in your segmented control.
Then the child (presumably the tab bar controller) can switch selected tabs in response to the message you define.

View controller with 2 destinations

I have 2 buttons in a view controller. If button 1 is clicked, then go to WebView controller. If button 2 is clicked then go to ArtView controller.
How to make override func prepare(for segue: UIStoryboardSegue, sender: Any?) have 2 destinations depending on the button clicked?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? WebViewController {
// your code
}
if let destination = segue.destination as? ArtViewController {
// your code
}
}
Hope it will help! :)
Have each button invoke a different segue, either by control-dragging segues directly from the button, or by calling performSegue(withIdentifier:sender:) from your buttons' IBAction code.
Then use code in prepare(for:sender:) to figure out which destination is being invoked.
You can:
Check the identifier on the segue (not recommended - fragile)
Check the class of the destination view controller (better than 1)
Have each view controller implement a protocol and check the protocol of the destination:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.destination {
case let webView as WebViewProtocol:
//Your code for a web view
case let artView as ArtViewProtocol:
//Your code for an Art View
}
}
Option 3 is reliable while providing loose coupling (each case can trigger any view controller that conforms to the desired protocol. All it needs to know is that the destination understands the desired protocol.)

navigation bar disappear after adding prepare function

I am woking on an recording APP.
I tried to add navigation controller in my first recording viewcontroller which then could pass filename array to the second viewcontroller using the following function prepare:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let controller = segue.destination as? MainViewController
controller?.recordArray = recordingArray
self.present(controller!, animated: true, completion: nil)
}
However, when ran in the simulator the navigation bar disappeared in the second controller and Xcode pops out the warning
Thread1: EXC_BAD_ACCESS(code=2,address=0x7fff51edfff8)
Has anyone got any advice?
Thanks!
Don't ever call present(_:animated:completion:) inside prepare(for segue:).
prepare(for segue) is called automatically by the system just before a segue is about to happen to let you prepare the data for sending to the destination viewcontroller or do any other calculations you need to before performing the segue. A segue needs to be set up in Storyboard and it will either be called automatically or if it is a manual segue, you need to call it using perform(segue) and once you do that, the system will call prepare(for segue) for you. You can see why calling another navigation function proves to be problematic, since you are trying to navigate to another viewcontroller using two different methods (segue and present).
If you haven't set up the segue in Storyboard, then you also need to do that, since if it is not set up, let controller = segue.destination as? MainViewController will be nil.
Once you set up the segue in Storyboard, this is how your function should look like:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let controller = segue.destination as? MainViewController {
controller.recordArray = recordingArray
self.present(controller, animated: true, completion: nil)
}
}
You shouldn’t be trying to present a VC in this method, it’s just a place for you to configure the destination VC before the segue presents it
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let controller = segue.destination as? MainViewController else { return }
controller.recordArray = recordingArray
}

iOS Swift - Data is not fetched quickly enough to reach another UIViewController

I have a source view controller and a destination view controller.
I would like to show data fetched from a url in the UI (destination) just after moving to this screen ("show" segue).
In order to do that, in the source VC I use "prepare for segue" method, where I call a function that returns an array with all the fetched data I want to show and pass it to the destination VC to be shown in a UITableView.
The problem is that many times the whole data is not fetched from the url, so I pass an empty array to the destination.
This is the code in the source VC:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVc = segue.destination as?
ShowCharacterInfoViewController{
fetchCharacterArrayFromUrl(characterName: cellContent){ results in
destinationVc.array.append(contentsOf:results)
destinationVc.tabelView.reloadData()}
} }
I can't think of a proper solution.
You can do one of two things.
Load the data and only perform the segue after the function has returned the data.
Transition to the destination screen and have that controller load the data in viewWillAppear.
// Add a place to save the results
var saveResults: MyResultsType?
#IBAction func someButtonAction(_ sender: Any) {
// Consider putting a "Loading..." dialog here
fetchCharacterArrayFromUrl(characterName: cellContent){ results in
self.saveResults = results
DispatchQueue.main.async({
// Hide the "Loading..." dialog
performSegue(withIdentifier: "ID of your Segue", sender: sender) //
})
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destinationVc = segue.destination as? ShowCharacterInfoViewController {
// I'm not sure append() is really what you need here
destinationVc.array.append(contentsOf: savedResults)
destinationVc.tabelView.reloadData()
}
}
You need to update the UI stuff in main thread since you are executing backend call:-
DispatchQueue.main.async({
destinationVc.tabelView.reloadData()
})

How can i create a segue that pass data to another view controller only if certain criteria are met

I want to create a segue to pass data to another view controller but there are certain criteria that must happen for the segue to happen. If possible i would prefer to use the segue Id instead of the dragging method.
this is an example Im trying to accomplish
#IBAction func SubmitButtonPressed(sender: AnyObject) {
if 1<0 {
// dont perform segue
}else{
//Perform segue
// i want to pass this data in the next VC
var data = "foo"
//this is my segue id i want o use to go to the Second VC
var segueId = "segueForgotPasswordTwo"
// second VC
var secondVc = "viewController2"
// Iwant to to use prepare for segue but im getting errors in the parameters
prepareForSegue(UIStoryboardSegue, sender: AnyObject?){
}
}
}
Your question is a bit unclear but I believe this is what you are looking for...
func someFunction(){
if //some condition {
//code
}else if //some condition {
//code
} else {
//perform segue by using the folowing line. Assign the identifier to the segue in the storyboard.
//Do this by first creating a segue by dragging from a view controller to the destination view controller. Be sure to drag from the VIEWCONTROLLER, to the destination VIEWCONTROLLER. DO NOT just drag from the button. Next, choose the type of segue (eg. show or present modally), and then type in an indentifier for this segue.
performSegueWithIdentifier("SegueIdentifier", sender: nil)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SegueIdentifier" {
//now find your view controller that you are seguing to.
let controller = segue.destinationViewController as! SomeViewController
//access the properties of your viewController and set them as desired. this is how you will pass data along
controller.property = someValue
}
}
Overview:
Hook the segue from the source view controller to the destination view controller (see left side red arrows)
Don’t hook it from the button to the destination view controller
Create an action for the button to do your custom condition check then perform segue
Screenshot:
Code:
var data = "foo"
#IBAction func buttonPressed(sender: UIButton) {
let someCondition = true
if someCondition {
performSegueWithIdentifier("showGreen", sender: self)
}
else {
performSegueWithIdentifier("showPink", sender: self)
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showGreen" {
let greenVC = segue.destinationViewController as! GreenViewController
// Make sure the data variable exists in GreenViewController
greenVC.data = data
}
}
You can implement the shouldPerformSegueWithIdentifier function in your ViewController. When the segue is triggered, this function can cancel the segue if it returns false, so you can simply include whatever logic is required in this function and return true/false as appropriate.

Resources