Swift - Accessing child label text from parent after addSubview - swift

Hi have an app that adds multiple subviews (UIViews) to the parent using:
if let page:UIViewController = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
scrollView.addSubview(page.view)
}
In RecentViewController I have a number of elements (labels, image views etc). I would like to set these from the parent view controller. Is this possible and if so any resources?
I have been searching for a while but I can only find info when segues are used.
Thanks

Since you are using storyboard you can create referencing outlets for views in RecentViewController by dragging them from storyboard to view controller class. Then you can access these properties directly as follow :
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
page.someChild.image = someImage // raw example
self.recentViewController = page
scrollView.addSubview(page.view)
}

You have most the work done for you, create a property in the parent view controller to keep the child view controller.
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
page.view.frame = self.frame
self.recentViewController = page
scrollView.addSubview(page.view)
}
Now you can access anything in the child view controller with self.recentViewController.<childControl>.
IMPORTANT NOTE: When setting up this kind of parent-child relationships, you should use the method from Creating Custom Container View Controllers
if let page = self.storyboard?.instantiateViewControllerWithIdentifier("recentVC") as? RecentViewController {
self.recentViewController = page
self.addChildViewController(page)
page.view.frame = self.frame
scrollView.addSubview(page.view)
page.didMoveToParentViewController(self)
}
I know this looks mysterious and unnecessary but it will prevent odd nearly untraceable issues in the future.
Oh, and if you ever need to remove the child view
self.recentViewController.willMoveToParentViewController(nil)
self.recentViewController.view.removeFromSuperview()
self.recentViewController.removeFromParentViewController()

Related

Swift, newbie question related to containerView. how to make Storyboard viewcontroller for child the same size as a container view

I've been trying to correctly implement a ContainerView in my practice app after watching and reading various tutorials. I initially tried creating the child by utilizing the storyboard and dragging the ContainerView onto the ViewController in question, and then utilizing the child ViewController that is then automatically created, but I need to have multiple child ViewControllers and I couldn't quite figure that out. I researched some programatic ways to do it and I successfully have it functioning the way I want. The only hiccup being that when I am viewing the child ViewControllers on my storyboard they are full size and do not correlate in size to my ContainerView. So I have to have a bit of trial and error in getting the objects I place in the child to fit in the ContainerView.
Can anyone give me some pointers on how I fix that? I've used the code below. The function runs when a button on the parent ViewController is touched. There are other associated child ViewControllers: child2, child3 that run depending on which button is pushed. I didn't include that extra code below for the sake of being concise.
private lazy var child1: PersonInfoChildView1Controller = {
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
var viewController = storyboard.instantiateViewController(withIdentifier: "Child1VC") as! PersonInfoChildView1Controller
viewController.person = self.person
addChild(viewController)
return viewController
}()
//MARK: ADD THE CHILD
private func add(asChildViewController viewController: UIViewController) {
containerView.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = containerView.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParent: self)
}
Try to set your child view controller constraints relative to the container view.
Edit your add method like this:
private func add(asChildViewController viewController: UIViewController)
{
viewController.view.translatesAutoresizingMaskIntoConstraints = false
containerView.addSubview(viewController.view)
viewController.view.leftAnchor.constraint(equalTo: containerView.leftAnchor).isActive = true
viewController.view.rightAnchor.constraint(equalTo: containerView.rightAnchor).isActive = true
viewController.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor).isActive = true
viewController.view.topAnchor.constraint(equalTo: containerView.topAnchor).isActive = true
// Notify Child View Controller
viewController.didMove(toParent: self)
}
Also you can create an extension to UIView class to pin the view to the parent view by just one line code
extension UIView {
func pin(to superview: UIView){
translatesAutoresizingMaskIntoConstraints = false
topAnchor.constraint(equalTo: superview.topAnchor).isActive = true
leadingAnchor.constraint(equalTo: superview.leadingAnchor).isActive = true
trailingAnchor.constraint(equalTo: superview.trailingAnchor).isActive = true
bottomAnchor.constraint(equalTo: superview.bottomAnchor).isActive = true
}
}
put this in a separate swift file with this method you can make the container view same size as the view controller
containerView.addSubview(viewController.view)
viewController.view.pin(containerView)
This will save you a lot of time in the future.

Passing a Dependency to Tab Bar View Controllers

I'm trying to refactor an app to use dependency injection for the core data stack.
There's a great explanation here:
http://cleanswifter.com/dependency-injection-with-storyboards/
My problem is that in my app the view controllers linked by the uittabbarcontroller are embedded in navigation controllers, so this code:
if let tab = window?.rootViewController as? UITabBarController {
for child in tab.viewControllers ?? [] {
if let top = child as? PersistenceStackClient {
top.set(stack: persistenceStack)
}
}
}
doesn't ever see them.
My instinct is that I need to change the for child in to something that references the child of the navigationcontroller, but all attempts have failed.
I'm thinking I need something along the lines of:
for child in tab.navigationController?.viewControllers
but that doesn't seem to do anything.
I think I have solved this:
if let tab = window?.rootViewController as? UITabBarController {
for child in tab.viewControllers ?? [] {
for nav in child.childViewControllers {
if let top = nav as? PersistenceStackClient {
top.setStack(stack: coreDataStack)
}
}
}
}

How to handle iOS 11 large title animation when using multiple container views?

I am making an app at the moment where 1 screen has a segmented control with 3 segments. Initially I had 1 table view and when you change segment I would simply change the data source/cell etc and reload the table. While this works great there is always the problem that when you change segments it will not remember your last scroll position because the table view gets reloaded.
I tried to get around this with storing offset position, rows etc but I could never get it to work like I wanted. Seems especially annoying when you have different cell types for the segments and they are self sizing as well.
I than decided to have a master view controller with the segmented control and 3 container views with their own VC and table view for each segment. I simply hide/show the correct container view when changing segments. This also works great but I have 1 problem with iOS 11 style large headers. Only the 1st container view added as a subview to the ViewControllers view manipulates the collasping/expanding of the title when you scroll.
Therefore when I change to the 2nd or 3rd container view and start scrolling I do not get the large title collapsing animation. How can I get around that?
I tried the following
1) Change Container view zPosition when changing segments
2) Move the container view to the front by calling view.bringSubview(toFront: ...)
3) Looping through the subviews and calling
view.exchangeSubview(at: 0, withSubviewAt: ...)
I believe I could remove all container views and add the one I need again and give them constraints but I wonder if there is a more straight forward solution.
Or if someone has a good solution to remember a tableViews scroll position before reloading it I would appreciate that too.
So I found an answer that seems to work for me, thanks to this great article. https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
Essentially what I did is
1) Remove the ContainerViews and Segues from the MasterViewController Storyboard.
2) Add a lazy property for each VC of the segmented control in the MasterViewController. They are lazy so that they only get initialised when you actually need them
lazy var viewController1: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
lazy var viewController2: LibraryViewController = {
let viewController = UIStoryboard.libraryViewController // convenience property to create the VC from Storyboard
// do other set up if required.
return viewController
}()
3) Create an extension of UIViewController with the following 2 methods. I added them in an extension purely for code organisation as they might be reused on other ViewControllers.
extension UIViewController {
func add(asChildViewController viewController: UIViewController) {
// Add Child View Controller
addChildViewController(viewController)
// Add Child View as Subview
view.addSubview(viewController.view)
// Configure Child View
viewController.view.frame = view.bounds
viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
func remove(asChildViewController viewController: UIViewController) {
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
}
4) Now in my segmented control method that gets called when you change segment I simply add the correct ViewController. The nice thing is that remove/adding them does not actually deallocate them.
func didPressSegmentedControl() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: viewController2)
add(asChildViewController: viewController1)
} else {
remove(asChildViewController: viewController1)
add(asChildViewController: viewController2)
}
}
5) Make sure you call the method at point 4 in ViewDidLoad so that the correct VC is added when the VC is loaded the 1st time.
func viewDidLoad() {
super.viewDidLoad()
didPressSegmentedControl()
}
This way when we remove a ChildViewController and add another one it will always be the the top VC in the subviews array and I get my nice title collapsing animation.
Another added benefit of this approach is that if you never go to a particular segment that particular VC will never get initialised, because they are lazy properties, which should help with efficiency.
Hope this helps somebody trying to do the same.
This is a horrible issue which I hope will be resolved soon, but there is another fix - although I freely admit that this is a nasty hack.
Basically, the issue only applies to the FIRST container view in the hierarchy. Add another container view to your hierarchy. Set it as hidden, then delete its segue and its target view controller just to be neat. Finally, make sure that this is the first container view in the hierarchy by dragging it to the top of the list.

NSPopover animate contentViewController change and change popover size

I am making a NSPopover, and have made it so I can transition between view controllers with a sliding animation using a parent view controller with my view controllers as children. It works well, except before I added this, the popover automatically resized to my view's size, but now, the popover is stuck at a fixed size.
The code for creating the popover:
self.homeVC = PopoverViewController(nibName: "PopoverViewController", bundle: nil)
self.loginVC = SignInViewController(nibName: "SignInViewController", bundle: nil)
self.containerView.view.wantsLayer = true
self.containerView.view.frame = self.homeVC!.view.bounds
self.containerView.addChildViewController(self.homeVC!)
self.containerView.view.addSubview(self.homeVC!.view)
popover.contentViewController = self.containerView
The code for transitioning the view controllers:
self.loginVC!.view.frame = self.homeVC!.view.bounds
self.containerView.addChildViewController(self.loginVC!)
let transition: NSViewControllerTransitionOptions = .SlideLeft
self.containerView.transitionFromViewController(self.homeVC!, toViewController: self.loginVC!, options: transition, completionHandler: { finished in
self.homeVC!.view.removeFromSuperview()
self.homeVC!.removeFromParentViewController()
self.containerView.view.bounds = self.loginVC!.view.bounds
})
Is there anyway that I can make the popover automatically resize to the size it is supposed to be after transitioning?
Thanks in advance.
I was able to fix it by instead of explicitly setting the frame, setting constraints to make the container view match the child's size.

ViewControllers inside UIScrollView not Visible swift

I'm trying to add ViewControllers in UIScrollView and VC's are loaded from xib. This is my code
let onevc = OneViewController()
self.addChildViewController(onevc )
self.scrollView.addSubview(onevc .view)
onevc.didMoveToParentViewController(self)
let twovc = TwoViewController()
var frame:CGRect = twovc.view.frame
frame.origin.x = 320
twovc.view.frame = frame
self.addChildViewController(twovc)
self.scrollView.addSubview(twovc.view)
twovc.didMoveToParentViewController(self)
self.scrollView.contentSize = CGSizeMake(640, self.view.frame.size.height)
self.scrollView.pagingEnabled = true
Both the viewcontrollers are added but they are not visible, scrollview is just showing white screen. I have added few elements in ViewControllers and also to test I added println("something") So when the view is loaded, It is printing something but not showing anything. what am I doing wrong here?
Perhaps you forgot to specify the nib name for the view controller?
let onevc = self.storyboard!.instantiateViewControllerWithIdentifier("OneViewController")
or
let onevc = OneViewController(nibName: "OneViewController", bundle: nil)
Same goes for the 2nd one.