I have a container view which performs a couple of segues inside based upon a users actions. I also have a top UIView that I would like to control from within the container view segue and set it's state.
I've tried to set a delegate to the home controller using a protocol and also the following approach:
if let parent = self.parent as? HomeController {
parent.handleTopBarState(state: .web)
}
What I would like to happen is when I'm on a specific view controller, set the parent view controller's top bar view's state.
Thanks.
If you have added child view controller then you can access simply with self.parent. To make sure put it in if let and cast
if let parent = self.parent as? ParentViewController {
parent.resetItemsCollectionView()
}
If you have embedded view controller on another view controller, you can message it's parent view controller with many ways.
In child (embedded) view controller, you can have reference to parent. Give identifier to the embedding segue in the storyboard, declare var with type of the parent in child, and assign the parent on prepare(for segue: UIStoryboardSegue, sender: Any?) function, and call any public methods of the parent.
Create protocol, make the parent confirm to the protocol, create var of the protocol in child, set parent as protocol object of the child in prepare(for segue: UIStoryboardSegue, sender: Any?), and call protocol methods whenever you want.
Just post notification to NotificationCenter on child, and observe the notification on parent. Documentation of NotificationCenter, here you can find a brief example of using it.
Hope this helps!
PS: You can also observe properties of the child in the parent vc, if you want that way, I can explain.
Related
I have a Detail View Controller and I want to show the same Detail View Controller but with a different item.
var nexttype : Type!
let nexttypeVC = TypeDetailVC()
#IBAction func buttonTapped(_ sender: UITapGestureRecognizer) {
nexttype = type.Array![0]
nexttypeVC.type = next.type
self.navigationController?.pushViewController(nexttypeVC, animated: true)
}
When I print the name or a identifier of the item I get the correct one, but when I launch the simulator I get error. The values of the labels and everything is nil.
How can I tell the view controller to reload again but with a different item?
Call performSegue(withIdentifier:) rather than pushViewController. You'll need to programmatically register a segue or add one via storyboard. Then use prepare(for segue: sender:) to give the destination ViewController the item you wish. Note that the destination's viewDidLoad() is called after the prepareForSegue.
This is the standard Apple-recommended way to seed the destination ViewController with data it needs prior to its load.
Instead of pushing the same view controller onto the navigation stack. Try to arrange all the logic to update UI elements into a method. Call this method in viewDidLoad(). Calling self.viewDidLoad() is not a good idea.
Use observer pattern and call the method that updates UI elements.
I have a view controller that use it as a login page.So I put login and cancel buttons and I want to use static table view for username and password fields. The problem is about static table view controller because I must put it inside a container view and have relation to it as a child view controller. I want to read text fields values when the user touches the login button.But because login button is inside parent view controller, I try to call exit segue to get values from text fields. But I can't do that and I don't know it's logical to do this action.
So in summary for this purpose is this way true or not and if true how can I do that?
You could do that by overriding prepare method.
1- From storyboard, give an identifier to the segue that embeds your login table view controller to the container, let's call it loginEmbedSegue.
2- Add a new property to the view controller that contains the container view like this:
var loginTableViewController: LoginTableViewController
3 - Inside the view controller that contains the container view, override prepare method in the following way:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "loginEmbedSegue" {
self.loginTableViewController = segue.destination as! LoginTableViewController
}
}
4- Now, you have a reference to the the view controller that contains the username and password textfields.
Good luck!
I'm new to swift and am trying to write an app with it.
I have a UIViewController that I am transitioning to. I have designed the UI in interface builder and I intend to use segues to manage the transition. However, the view controller relies on data that is passed into the view controller from the previous view controller.
If I have properties on my view controller then I will need to redefine my init method. But I wouldn't normally call the init method; it would be called for me before prepareForSegue. So I see a few possible solutions:
Make my variables optional (so I can pass them in prepareForSegue
and update the view then).
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController: EventViewController = segue.destinationViewController as! EventViewController
viewController.event = self.event
}
Manually init my view controller and present it programmatically instead.
???
Is there a third option? If not, which of the previously mentioned 2 is better practice?
There is two possible options as you mentioned:
The first one is the easiest which is to pass the data in prepareForSegue. which you don't have to care about dismissing the controller or keeping a track of inner view controllers,because storyboard will take care of it.
The second way is to set a Storyboard ID in storyboard,for the controller you need to present programmatically, which need more things to handle, like to dismiss the controller or keep track of inner presented controllers.
let nextViewControllerName = storyboard?.instantiateViewControllerWithIdentifier("Storyboard ID") as! nextViewControllerName
nextViewControllerName.event = self.event
self.presentViewController(nextViewControllerName, animated: true, completion: nil).
At the end they does the same purpose.
Note: You should always pass the data before presenting the controller.
I've tried creating a custom view controller for a share extension.
A confusing situation happens when I present another view controller on top of the initial view controller that was set on the MainInterface.storyboard. This presented view controller is embedded in a navigation controller (it's the root view controller of it).
I did a check on the presentingViewController
(lldb) po [self presentingViewController]
<_UIViewServiceViewControllerOperator: 0x7a978000>
(lldb) po [[self presentingViewController] extensionContext]
nil
So, the extension context is nil at this point. I could access the extensionContext by passing it around from the presentingViewController to the presentedViewController.
But, I found this behavior is a bit strange. Is the app extension designed to only be accessed from one level of view controller hierarchy?
If you're going to use more than a single view controller in your extension storyboard, you'll have to pass a reference to the extensionContext of the original view controller to the view controller that will ultimately be responsible for completing the extension's request. In the initial view controller:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let destination = segue.destinationViewController as! FinalViewController
destination.originalExtensionContext = self.extensionContext
}
And in your final view controller:
#IBAction func dismissController(sender: UIButton!) {
dismissViewControllerAnimated(true) { () -> Void in
self.originalExtensionContext.completeRequestReturningItems(self.originalExtensionContext.inputItems, completionHandler: nil)
}
Note that you have to create a uniquely named property for the original extension context, since extensionContext already exists as a property name on the superclass UIViewController. You can't pass the existing extensionContext to the UIViewController's property extensionContext as it is a read-only attribute.
The view controller being presented by a view controller should have no problem using the parent's extension. Taking a look at the documentation:
The view controller can check this property to see if it participates in an extension request. If no extension context is set for the current view controller, the system walks up the view controller hierarchy to find a parent view controller that has a non nil extensionContext value.
Therefore, if you can be certain of the fact that your root view controller does indeed have an extensionContext, any view controller presented by this view controller should have access to it, simply through it's own extensionContext property.
Note: If this is not the behaviour you a re observing, this may be a bug with the SDK, and I would recommend filing a radar.
While it's not the best approach for clean code and architecture, it's quite handy:
In root extension controller where extensionContext exists:
final class ShareRootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
NSExtensionContext.shared = self.extensionContext
}
}
extension NSExtensionContext {
fileprivate(set) static var shared: NSExtensionContext!
}
In any other view controller:
let context = NSExtensionContext.shared
I have a complex iPad view that I manage by having several view controllers. I previously (before iOS6/Xcode 4.5) did this by allocating my view controllers in code, and hooked up the various views to them though links to the master view.
What I would like to do is use the new UIContainerView container views to embed the view controllers in the storyboard file. I don't seem to be able to make an IBOutlet link to the embedded view controller to the master controller.
Is it possible to do this? Or to retrieve the embedded controller via a tag or something in the code?
This question is SPECIFICALLY about using container views
Another option for some cases is to capture the embedded controller using -prepareForSegue:sender:.
For example, if I have a UINavigationController embedded within a CustomContainerViewController, I can name the embed segue embedContentStack in the storyboard and capture it in CustomContainerViewController via
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"embedContentStack"]) {
// can't assign the view controller from an embed segue via the storyboard, so capture here
_contentStack = (UINavigationController *)segue.destinationViewController;
}
}
I'm not sure what you mean by "retrieve the embedded controller". When you want to use a controller you use the UIStoryboard method instantiateViewControllerWithIdentifier:, using the identifier that you give to the controller in IB. You can also use the performSegueWithIdentifier:sender: method (which also instantiated the view controller). You should check out the "Using View Controllers in Your App" section in the Apple docs. It also makes reference to the fact that child view controllers are instantiated at the same time as the container controller.
After edit: If you embed a container view in another view controller, that embedded view's controller can be referenced from the containing controller with self.childViewControllers (which will be an array, so if there is just one, you can get it with lastObject).
Here is another thread about it: Access Container View Controller from Parent iOS
They propose to keep a reference in prepareForSegue or search for the embedded viewController in self.childViewControllers
Note of Caution
Before proceeding to use an answer to this question, you may wish to reflect whether the embedded things really need to be view controllers.
Eg, if you're embedding a UICollectionViewController subclass, could you instead embed a UICollectionView subclass? Or, even better, could you embed a UIView subclass that hides away the UICollectionView behind a simple ViewModel?
In the code base I'm currently working on, I'm embedding two view controllers in to another view controller. Both could fairly easily be plain views instead, and could then be more easily bound to in the storyboard, without this messy code.
Unfortunately, they are currently view controllers and I'm not in a position to simplify them in to plain views right now, so this will have to do.
Background
I'm using the approach of picking up the embed segue in prepare(for segue:, sender:) as suggested by Playful Geek here.
I wanted to show the swift I'm using for this, as it seems to be fairly tidy…
class EditionLandingViewController: UIViewController {
fileprivate var titlesView: SectionTitlesViewController!
fileprivate var sectionsView: SectionsViewController!
}
//MARK:-
extension EditionLandingViewController {
private enum SegueId: String {
case embedTitles
case embedSections
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
guard
let segueRawId = segue.identifier,
let segueId = SegueId(rawValue: segueRawId)
else { return }
switch segueId {
case .embedTitles:
self.titlesView = segue.destination as! SectionTitlesViewController
case .embedSections:
self.sectionsView = segue.destination as! SectionsViewController
}
}
}
Discussion
I've chosen to name segues as action methods.
Using an enum cases for segue identifiers means you've got the compiler and tooling on your side, so its much harder to get a segue name wrong.
Keeping the segue ids in a private enum within the extension scope seems appropriate in this case as these segues are not needed anywhere else (they can't be performed, for example).
I'm using implicitly unwrapped types for the embedded view controllers because (in my case anyway) it's a logic error if they are missing.
Similarly, I'm also happy to force cast the destination view controller types. Again, it would be a logic error if these types are not the same.
Swift version of the top-voted Answer. Years later, Interface Builder still does not seem to support dragging IBOutlets to embedded Container Views.
Instead, set the outlets in prepare(for:sender:):
#IBOutlet var someViewController: SomeViewController!
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EmbedSomeViewController", let destination = segue.destination as? SomeViewController {
someViewController = destination
}
}
You must also set up the UIContainerView on your Storyboard. Xcode will generate an embed segue automatically; set the segue's Identifier.