Is it fine to nest a UIViewController within another without using addChildViewController? - ios

I'm trying to make a custom ContainerViewController, but due to lots of difficulties with the ViewController transitions and making everything interactive, I've decided to mimic that functionality myself.
What I basically want to do, is have a paginated UIScrollView (the HeaderView) on the top control different another UIScrollView (the ControllersView) below that contains ViewControllers as pages so that as you swipe to a new page on the HeaderView, it also swipes to the next viewcontroller on the ControllersView. This is what the setup would look like.
My question is, is there anything wrong with having the aforementioned setup? All I'll do to add the view controllers to the ControllersView is just something like: controllersView.addSubview(pagecontroller1.view).
Some posts online seem to say that "the appropriate ViewController functions won't be called" or whatever. What do I seem to be missing here? I'm guessing there's a lot of dismissing and admitting of ViewControllers that I need to call every time a ViewController is out of frame right?
To clarify the question: Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call? I'm realizing that if I were to set things up this way, I need to manage a lot of things that are usually handled automatically, but as I mentioned before, custom ContainerViewControllers have failed me and I'm going with this.
PS. If you seem to still be lost on how this will look like, see my previous question here where I originally wanted to use a Container ViewController. There's a much better mockup there.

You can add and remove VC In Container Views
For - Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call?
As, We need to call WillAppear and Disappear Func when Adding and removing a VC , Thus Try using below Functions That will Handle these Responses
I use the Two specific Functions to add and remove Controller in ContainerView/UIView/SubView in ScrollView inside a UIView
To Add
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
To Remove
private func remove(asChildViewController viewController: UIViewController)
{
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
secondContainer.willRemoveSubview(viewController.view)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
Creating Object
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
For - controllersView.addSubview(pagecontroller1.view)
Answer - Yes Approbate func wont be called if pagecontroller1 is not loaded in to memory stack, to load that you need to notify pagecontroller1 that it is going to be added to memory stack as Child View , Just as We initiate a Controller and basically notifies the Controller to get its component loaded to memory stack to get some memory allocations
For Question - Is it fine to nest a UIViewController within another without using addChildViewController?
Check apple Documentation - https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchildviewcontroller
This is necessary just as to notify the controller who is going to be added in Another Parent View as Child
Sample Project
https://github.com/RockinGarg/Container_Views.git
Or
https://github.com/RockinGarg/ContainerView-TabBar.git
If Question is Still not answered Please Tell me what Func Exactly you want to handle by yourself

Related

Can't make custom segue in Xcode 10/iOS 12

I'm having the hardest time implementing a presentation of a drawer sliding partway up on the screen on iPhone.
EDIT: I've discovered that iOS is not respecting the .custom modalTransitionStyle I've set in the Segue. If I set that explicitly in prepareForSegue:, then it calls my delegate to get the UIPresentationController.
I have a custom Segue that is also a UIViewControllerTransitioningDelegate. In the perform() method, I set the destination transitioningDelegate to self:
self.destination.transitioningDelegate = self
and I either call super.perform() (if it’s a Present Modal or Present as Popover Segue), or self.source.present(self.destination, animated: true) (if it’s a Custom Segue, because calling super.perform() throws an exception).
The perform() and animationController(…) methods get called, but never presentationController(forPresented…).
Initially I tried making the Segue in the Storyboard "Present Modally" with my custom Segue class specified, but that kept removing the presenting view controller. I tried "Present as Popover," and I swear it worked once, in that it didn't remove the presenting view controller, but then on subsequent attempts it still did.
So I made it "Custom," and perform() is still being called with a _UIFullscreenPresentationController pre-set on the destination view controller, and my presentationController(forPresented…) method is never called.
Other solutions dealing with this issue always hinge on some mis-written signature for the method. This is mine, verbatim:
public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
I've spent the last four days trying to figure out “proper” custom transitions, and it doesn't help that things don’t seem to behave as advertised. What am I missing?
Instead of using a custom presentation segue, you could use a Container View for your drawer. This way, you can use a UIViewController for your Drawer content, while avoiding the issue with the custom segue.
You achieve this in two steps:
First pull a Container View into your main view controller and layout it properly. The storyboard would look like this: (You can see you have two view controllers. One for the main view and one for the drawer)
Second, you create an action that animates the drawer in and out as needed. One simple example could look like this:
#IBAction func toggleDrawer(_ sender: Any) {
let newHeight: CGFloat
if drawerHeightConstraint.constant > 0 {
newHeight = 0
} else {
newHeight = 200
}
UIView.animate(withDuration: 1) {
self.drawerHeightConstraint.constant = newHeight
self.view.layoutIfNeeded()
}
}
Here, I simply change the height constraint of the drawer, to slide it in and out. Of course you could do something more fancy :)
You can find a demo project here.

How can I create an instance of a custom model with multiple viewcontrollers in Xcode with Swift 4?

I'm new to Swift and I'm sure this question is pretty basic and has been asked and answered before.
I am not using storyBoard. My main viewController is created from AppDelegate via code.
I have:
a custom class defined in a model.swift file
a main viewController (from AppDelegate) that I am using as a container
3 additional viewcontrollers as subviews of the main (not each other)
all 3 subviews are displayed simultaneously each covering 1/3 of the screen (no segues)
each viewcontroller is in a separate .swift file
I want to create an instance of my custom class in the main viewController and have all 3 of the subviews be able to reference that instance.
Each of the subview view controllers need to be able to get/set instance variables and the other subviews need to be made aware of those changes.
I think I will need to use notifications to communicate the changes to the multiple subviews - but I haven't even begun to try and figure that out yet.
If this has been asked and answered before - could someone please either provide a link - or provide me with the right search terms so that I'm able to find the answer? The only found answers I've found that come close are to use segues to pass the data back and forth.
You can use delegate pattern. Below code is assuming that you are using MVVM pattern. (It is very similar for VIPER/ReSwift patterns also)
protocol DataChangedDelegate {
func refreshData()
}
// ViewModel for FirstViewController
class FirstViewModel {
var delegate: DataChangedDelegate?
var data: Any {
didSet {
delegate?.refreshData()
}
}
//rest of the things
}
//similarly other two view models will have a delegate and on data change will call the refresh method
And your view controllers should adopt this protocol
class FirstViewController: UIViewController, DataChangedDelegate {
//view controller code
//delegate code
func refreshDate() {
//tableView.reloadDate()
//collectionView.reloadDate()
//lableView.text = viewModel.data()
}
}
And where ever you create a viewControllers and add as subView, you have to set the delegate of viewModel.
let firstViewController: FirstViewController = createFirstViewController()
let firstViewModel = FirstViewModel()
firstViewModel.delegate = firstViewController
firstViewController.viewModel = firstViewModel
mainViewController.addSubView(firstViewController.view)
Similarly for all other view controllers.
Here's how I would do it:
Create a singleton class.
Configure the singleton's properties in the the main ViewController.
Use didSet to post a Notification.
Add a listener for that Notification in your additional ViewControllers.

ViewDidAppear not called for child view added to rootViewController

I am trying to display a child view controller over the top of all elements on screen (including navigation bars), and the only way I've found that works is to add it as a child view controller to my window's rootViewController:
guard let window = UIApplication.shared.keyWindow,
let view = window.rootViewController?.view
else { return }
window.rootViewController?.addChildViewController(attachmentViewController)
view.addSubview(attachmentViewController.view)
attachmentViewController.view.snp.makeConstraints { make in
make.left.equalTo(view)
make.right.equalTo(view)
make.top.equalTo(view)
make.bottom.equalTo(view)
}
attachmentViewController.didMove(toParentViewController: window.rootViewController)
However, this doesn't call the viewDidAppear or viewWillDisappear methods... Why is that? I really need it to.
Instead of doing all that, simply present the view controller (don't push it as suggested).
let destination = SomeViewController.instantiateFromStoryboard(self.storyboard!)
present(destination, animated: true, completion: nil)
Focusing on the "why is that?" of your question.
When you call addChildViewController to a view you're not changing the "stack" of view controllers at all or the state of the host view controller; you're just adding a view controller as a child controller of the main view.
Usually when you work with child view controllers you orchestrate calls like willMove and didMove to trigger the view controller lifecycle behaviour.
In your case, you may be better off with a push or present. Present will give you the capability of overlaying a view controller.
As a note, I have used an approach similar to what you describe for managing sign in/out states adding either a signed in child view controller or a signed out view controller. In which case, when they change I usually call methods like:
// To add the child
addChildViewController(child)
view.addSubview(child.view)
child.didMove(toParentViewController: self)
// To remove the child.
child.willMove(toParentViewController: nil)
child.removeFromParentViewController()
child.view.removeFromSuperview()

iOS - How to use a small view in different view controllers in Swift

I have a progress bar (with its own controller). This bar is supposed to be shown in different views depending on which view is visible. As the progress will be same, If possible I don't want to create many progress bar in many views rather I want to use same instance in all these views. Also in that way when I need to change any property of the progress bar it will be reflected commonly, which is required.
Please suggest me how can I use this common view. And also if my strategy is wrong, what would be the better design for such scenarios.
1) Well you have 2 options. You can declare a new Class ViewBox (or whatever name) and then use that inside your code
First View Controller
var box:ViewBox = ViewBox()
When you segue or transition to your next screen, you can have a predefined variable var box:ViewBox!. Then say when you press a button, the button has a function called transition.
//Now setup the transition inside the Storyboard and name the identifier "toThirdViewController"
override func prepareForSegue(segue:UIStoryboardSegue, sender:AnyObject?) {
if(segue.identifier == "toThirdViewController") {
var vc = segue.destinationViewController as! `nextViewController` //The class of your next viewcontroller goes here
vc.box = self.box
}
//Since The SecondViewController doesn't need ViewBox, we don't need it there.
}
where
nextViewController:UIViewController {
var box:ViewBox!
}
Or you could do a much simpler way and that is to look up a UIPageViewController :)

How do I refer to the title of a view controller from an embedded container view's view?

I have a UIViewController (let's call it "EditViewController") which has a Container View on it (call it "ContainerView") where I switch in and out various subviews (call the one I'm most concerned with "EditDetailsView").
From the EditDetailsView I need to change the title in the navigation bar of the EditViewController. I can't seem to be able to figure out how to reference it.
From inside EditViewController I can simply make a statement like:
self.title = #"Some new title";
and it changes just fine.
But from the EditDetailsView view that is currently the subview of ContainerView nothing seems to work:
self.title = ... is obviously wrong.
super.title = ... doesn't work and seems wrong anyway.
super.super.title = ... errors out as super is not a property found on UIViewController.
Can someone please tell me how to reference the title? I'm kinda lost.
Thanks!
While digging through the parentViewController chain is possible, it is error prone and unrecommended. It is considered a bad design. Imagine you set up your view controller hierarchy in some manner, but after a few months change it a bit and now there is one level deeper. Or, you would like to use the same view controller in several different scenarios. A much better design would be to pass the new title to the container view controller using delegation. Create a delegate protocol, with a method for setting the title.
- (void)childViewController:(ChildViewController*)cvc didChangeToTitle:(NSString*)title;
I know this is an old thread, but in case someone else needs it: to avoid boilerplate code with delegation, and avoid digging into the parentViewController, I did it the other way around.
I've referenced the child view controller from the parent and got their title. So no matter which child you show, you will always get the right title.
This is in Swift 3.
So, basically, this is your parent:
class EditViewController: UIViewController {
override func viewDidLoad() {
if let child = self.childViewControllers.first {
self.title = child.title
}
}
}
And this is your child:
class ContainerView: UIViewController {
override func viewDidLoad() {
self.title = "Sbrubbles"
}
}
Another good way to avoid excess code with delegation is to use RxSwift, if you are familiar to Reactive programming.

Resources