I have a view controller that contains a collectionView with 2 sections. The header of the second section is a sticky header and it has a segmentedControl inside of it:
ParentViewController
--collectionView
--sectionOne // because there is specific data in sectionOne I cannot use a PageViewController
--sectionTwo
sectionTwoHeader // sticky header
[RedVC, BlueVC, GreenVC] // these should be the size of sectionTwo
When a segment is selected I'm using a ContainerVC that will show a view controller corresponding to each segment:
// each of of these color vcs have collectionViews inside of them
RedCollectionViewController(), BlueCollectionViewController(), GreenCollectionViewController()
The problem is when the segment is selected the collectionView isn't showing any of the color view controllers it's supposed to show. How do I add each color vc using addChildViewController() to a collectionView?
The collectionView w/ segmentedControl's selectedIndex:
class ParentViewController: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
var collectionView: UICollectionView!
var containerController: ContainerController!
var vc: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
containerController = ContainerController()
}
#objc func selectedIndex(_ sender: UISegmentedControl){
let index = sender.selectedSegmentIndex
switch index {
case 0:
containerController.vcIdentifierReceivedFromParent(segment: "BlueVC")
break
case 1:
containerController.vcIdentifierReceivedFromParent(segment: "RedVC")
break
case 2:
containerController.vcIdentifierReceivedFromParent(segment: "GreenVC")
break
default: break
}
/*
// because of the X and Y values this adds the containerVC over the collectionView instead of under the sectionTwo segmented Control header
vc = containerController
addChildViewController(vc)
vc.view.frame = CGRect(x: 0,y: 0, width: collectionView.frame.width,height: collectionView.frame.height)
view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
lastViewController = vc
*/
}
}
ContainerVC:
class ContainerController: UIViewController {
var vc: UIViewController!
var lastViewController: UIViewController!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
vcIdentifierReceivedFromParent(segment: "RedVC")
}
func vcIdentifierReceivedFromParent(segment: String){
switch segment {
case "RedVC":
let redVC = RedCollectionViewController()
addVcToContainer(destination: redVC)
break
case "BlueVC":
let blueVC = BlueCollectionViewController()
addVcToContainer(destination: blueVC)
break
case "GreenVC":
let greenVC = GreenCollectionViewController()
addVcToContainer(destination: greenVC)
break
default: break
}
}
func addVcToContainer(destination: UIViewController) {
//Avoids creation of a stack of view controllers
if lastViewController != nil{
lastViewController.view.removeFromSuperview()
}
self.vc = destination
addChildViewController(vc)
vc.view.frame = CGRect(x: 0,y: 0, width: view.frame.width,height: view.frame.height)
view.addSubview(vc.view)
vc.didMove(toParentViewController: self)
lastViewController = vc
}
}
You are adding Red / Blue / Green VCs to Container View controller that is referenced from inside ParentViewController. But you are adding each of them inside ContainerVC topmost view, whose frame is probably never set, as far as I can see from your code.
It's probably CGRectZero.
Adding child VC views to this view will result in they are getting wrongly positioned, or not positioned at all. Because Container View controller is nowhere in the view controller hierarchy. You are effectively doing everything within ParentViewController's viewDidLoad(). Most probably, ContainerVC's viewDidLoad is not even called. Hence its view is never initialised properly.
You probably do not need ContainerVC at all. Try adding children to ParentViewController, and try adding them after viewDidLoad() call, i.e. in viewDidAppear(), viewDidLayoutSubviews() and upon switch segment selection.
Related
I would like to add a childViewController into a custom UIView part of a parentViewController. However, if I am doing self.customView.addSubview(childViewController.view) I cannot see the childViewController.view as it doesn't get added. In contrast, if I do self.view.addSubview(childViewController.view) it all works well. Can someone explain why this is happening? I really need to add childViewController.view as subview of the customView and not as part of the self.view.
if let childViewController = self.storyboard?.instantiateViewController(withIdentifier: "ChildVC") as UIViewController? {
self.addChildViewController(childViewController)
childViewController.view.frame = customView.bounds
self.customView.addSubview(childViewController.view)
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParentViewController: self)
childViewController.view.frame = CGRect(x: customView.frame.origin.x,
y: customView.frame.origin.y,
width: customView.frame.width,
height: customView.frame.height)
}
How about using a Container view from Object Library (place from where we drag table view, textView and all UI components onto our storyboard). which is
Container view define a region of view controller that include
a child view controller
When you take Container view from object library in your desired
ViewController on storyBoard.
it automatically gives you a another view controller attached to your view controller with a segue.
you just need to override this segue code and that dragged container view will work as a child view controller for you it did load will call automatically.
just Override this is your parent view Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "SegueIndetifierName":
(segue.destination as? YourChildViewController)?.parentViewControllerObject = self
default:
break
}
}
}
NOTE: declare parentView controller object in your child view controller like this
weak var parentViewControllerObject: ParentViewController!
to uniquely identify the relation ship between child and parent. and rest your work will be done automatically.
You can check the Sample Working project to add Subviews
Link https://github.com/RockinGarg/Container_Views.git
Required Code:
Class Object to be added as Subview
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
}()
Add in ParentView as Subview
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)
}
Where : -
firstContainer is the parent view In which subview is to be added
viewController class object whose view is to be added as Subview
Note- This can be used in containerViews as well as Normal UIView too for adding a Controller as Subview
I'm doing radio player in my app. And want to have widget with info + control buttons, that will be persisted over all controllers in app while playing. Like in itunes or like google chromecast container that will push from bottom all the elements of other viewcontroller (not overlay elements)
I know that overlay view can be added in appdelegate to keywindow, as GoogleCast Container added:
let appStoryboard = UIStoryboard(name: "Main", bundle: nil)
let navigationController = appStoryboard.instantiateViewController(withIdentifier: "MainNavigation")
let castContainerVC: GCKUICastContainerViewController = GCKCastContext.sharedInstance().createCastContainerController(for: navigationController)
castContainerVC.miniMediaControlsItemEnabled = true
self.window = UIWindow(frame: UIScreen.main.bounds)
self.window?.rootViewController = castContainerVC
self.window?.makeKeyAndVisible()
But I can't understand, how I can design and instantiate my controller + add it to window + hide when nothing is playing.
I forgot to mention, that app is already up and running. There are about 30 view controllers inside navigation bar
I would create a window on top of the current window to which I would put that radio view. Then the radio view would be always on top of the view hierarchy in the standard window. To allow touch events be processed by the standard window that is under the radio window, you would need to override hitTest in the radio window to make sure it wont process events that are not supposed to be processed by it.
You can take this as an example:
import UIKit
class RadioController: UIViewController {
fileprivate struct Static {
static let window: UIHigherWindow = {
let window = UIHigherWindow(frame: UIScreen.main.bounds)
window.rootViewController = RadioController()
window.windowLevel = UIWindowLevelAlert - 1
return window
}()
}
override func loadView() {
// use passView, as it will pass the touch events tu underlying window
self.view = PassView()
self.view.backgroundColor = UIColor.clear
}
override func viewDidLoad() {
super.viewDidLoad()
// configure whatever subview you want, in your case the radio view
let radioView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 50, width: UIScreen.main.bounds.width, height: 50))
radioView.backgroundColor = .red
self.view.addSubview(radioView)
}
override var preferredStatusBarStyle: UIStatusBarStyle {
// this will now define the status bar style, as it will become the topmost window for most of the time
return .default
}
static var showing: Bool {
get {
return !Static.window.isHidden
}
}
static func present() {
Static.window.isHidden = false
}
static func hide() {
Static.window.isHidden = true
}
}
// MARK: Passing touch events to the back view
class PassView: UIView {}
class UIHigherWindow: UIWindow {
// this will allow touch events to be processed by the default window
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, with: event)
if hitView!.isKind(of: PassView.self) {
return nil
}
return hitView
}
}
Solution
Embed your initial view controller inside a container view controller
Add your subviews to the bottom of the view for the container vc
Align the subviews so that they are offscreen below the main view
Create a method that when called animates the subview up from the
bottom to the appropriate position and another to animate the
subview back below the bottom
Place calls to the animation methods where appropriate in your code
Notes
You will want to add a custom class to your container vc so you can
call the animation methods on it
A UIViewcontroller has a titleView at the top, an UIContainerViewwhich manage 2 TableviewController, and the last item is an UISegmentedControl which is used to change between LoginTableViewController and RegisterTableViewController which have 15 cells, so it's needed scroll on them.
LoginTableViewController and RegisterTableViewController are Statics Table Views but can't scroll, that means that if I Scroll it, it will immediately returns to the top.
Also I have another ViewController which has a containerView with a UITableViewController inside but it isn't static and it can scroll.
Here is my code of segmenteControlAction
#IBAction func selectTab(_ sender: UISegmentedControl) {
let nextViewController: UITableViewController!
switch sender.selectedSegmentIndex {
case 0:
let loginViewC = LoginTableViewController.instantiateFromStoryboard()
loginViewC.isPresentingFromMenu = isPresentingFromMenu
nextViewController = loginViewC
break
default:
let regiterViewC = RegisterTableViewController.instantiateFromStoryboard()
regiterViewC.isPresentingFromMenu = isPresentingFromMenu
nextViewController = regiterViewC
break
}
if let currentViewController = currentViewController{
currentViewController.willMove(toParentViewController: nil)
currentViewController.view.removeFromSuperview()
currentViewController.removeFromParentViewController()
}
currentViewController = nextViewController
addChildViewController(currentViewController!)
containerView.addSubview(currentViewController!.view)
currentViewController!.didMove(toParentViewController: self)
}
Do you know what I'm missing ?
EDITED
If I set the tableViewControllerwhich has 15 cells as embed view of ContainerView, the scroll works, but when I change the childViews in the container the scroll don't works
I want to scrolling view when navigationItem clicked.
But..here is some error.
I want to make snapchatlike scroll view.
It works fine, but... when I call scrollView out of viewdidload function, scrollView is nil!
I already make snapchat like swipe view. but I want to make swipe view when navigation Item(heart mark) clicked.
My ViewController viewdidload func - it works fine
#IBOutlet weak var scrollView: UIScrollView!
var V1 : LeftViewController = LeftViewController(nibName: "LeftViewController", bundle: nil)
var V2 : CenterViewController = CenterViewController(nibName: "CenterViewController", bundle: nil)
var V3 : RightViewController = RightViewController(nibName: "RightViewController", bundle: nil)
override func viewDidLoad() {
super.viewDidLoad()
self.addChildViewController(V1)
self.scrollView.addSubview(V1.view)
V1.didMoveToParentViewController(self)
self.addChildViewController(V2)
self.scrollView.addSubview(V2.view)
V2.didMoveToParentViewController(self)
self.addChildViewController(V3)
self.scrollView.addSubview(V3.view)
V3.didMoveToParentViewController(self)
var V2Frame : CGRect = V2.view.frame
V2Frame.origin.x = self.view.frame.width
V2.view.frame = V2Frame
var V3Frame : CGRect = V3.view.frame
V3Frame.origin.x = 2 * self.view.frame.width
V3.view.frame = V3Frame
println(self)
println(scrollView)
println(V2.view.frame)
self.scrollView.setContentOffset(CGPointMake(V2Frame.origin.x, self.view.frame.size.height), animated: false)
self.scrollView.contentSize = CGSizeMake(self.view.frame.width * 3, self.view.frame.size.height) }
My ViewController clickEvent func - why scrollView is nil?
func clickEvent() {
self.scrollView?.scrollRectToVisible(V2.view.frame, animated: true)
println(self)
println(scrollView)
println(V2.view.frame)
//self.scrollView.setContentOffset(CGPointMake(V2.view.frame.origin.x, self.view.frame.size.height), animated: true)
}
I call clickEvent in Viewcontroller from other CenterViewController
- it can call fine but...in ViewController clickEvent functions' scrollView is nil.. How can I fix it?
func clickEvent(sender: AnyObject) {
ViewController().clickEvent()
}
in your code you get and set a view frame In viewDidLoad. In viewDidLoad views are only loaded in memory but not yet in screen, so if you try to ask a view for its frame in viewDidLoad you will Not get a consistante value..
Instead use viewWillAppear where you can ask view for its frame and you get a right value since the view is ready to be shown.
The class itself does not contain the layout information in it. So there is no way the view controller object know which view object is connected to which IBOutlet. The view controller have to be instantiated through storyboard itself. You can use the following methods
storyboard?.instantiateInitialViewController()
storyboard?.instantiateViewControllerWithIdentifier("YOUR_VC_STORYBOARD_ID")
When you use the second method don't forget to set the Storyboard Identifier.
You can find the place to enter the identifier when you go to View Controller -> Identity Inspector -> Storyboard_ID
I have found few posts for this problem but none of them solved my issue.
Say like I've..
ViewControllerA
ViewControllerB
I tried to add ViewControllerB as a subview in ViewControllerA but, it's throwing an error like "fatal error: unexpectedly found nil while unwrapping an Optional value".
Below is the code...
ViewControllerA
var testVC: ViewControllerB = ViewControllerB();
override func viewDidLoad()
{
super.viewDidLoad()
self.testVC.view.frame = CGRectMake(0, 0, 350, 450);
self.view.addSubview(testVC.view);
// Do any additional setup after loading the view.
}
ViewControllerB is just a simple screen with a label in it.
ViewControllerB
#IBOutlet weak var test: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
test.text = "Success" // Throws ERROR here "fatal error: unexpectedly found nil while unwrapping an Optional value"
}
EDIT
With the suggested solution from the user answers, ViewControllerB in ViewControllerA is going off the screen. Grey border is the frame I have created for the subview.
A couple of observations:
When you instantiate the second view controller, you are calling ViewControllerB(). If that view controller programmatically creates its view (which is unusual) that would be fine. But the presence of the IBOutlet suggests that this second view controller's scene was defined in Interface Builder, but by calling ViewControllerB(), you are not giving the storyboard a chance to instantiate that scene and hook up all the outlets. Thus the implicitly unwrapped UILabel is nil, resulting in your error message.
Instead, you want to give your destination view controller a "storyboard id" in Interface Builder and then you can use instantiateViewController(withIdentifier:) to instantiate it (and hook up all of the IB outlets). In Swift 3:
let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
You can now access this controller's view.
But if you really want to do addSubview (i.e. you're not transitioning to the next scene), then you are engaging in a practice called "view controller containment". You do not just want to simply addSubview. You want to do some additional container view controller calls, e.g.:
let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
addChild(controller)
controller.view.frame = ... // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
view.addSubview(controller.view)
controller.didMove(toParent: self)
For more information about why this addChild (previously called addChildViewController) and didMove(toParent:) (previously called didMove(toParentViewController:)) are necessary, see WWDC 2011 video #102 - Implementing UIViewController Containment. In short, you need to ensure that your view controller hierarchy stays in sync with your view hierarchy, and these calls to addChild and didMove(toParent:) ensure this is the case.
Also see Creating Custom Container View Controllers in the View Controller Programming Guide.
By the way, the above illustrates how to do this programmatically. It is actually much easier if you use the "container view" in Interface Builder.
Then you don't have to worry about any of these containment-related calls, and Interface Builder will take care of it for you.
For Swift 2 implementation, see previous revision of this answer.
Thanks to Rob.
Adding detailed syntax for your second observation :
let controller:MyView = self.storyboard!.instantiateViewControllerWithIdentifier("MyView") as! MyView
controller.ANYPROPERTY=THEVALUE // If you want to pass value
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChildViewController(controller)
controller.didMoveToParentViewController(self)
And to remove the viewcontroller :
self.willMoveToParentViewController(nil)
self.view.removeFromSuperview()
self.removeFromParentViewController()
This code will work for Swift 4.2.
let controller = self.storyboard!.instantiateViewController(withIdentifier: "secondViewController") as! SecondViewController
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
For Add and Remove ViewController
var secondViewController :SecondViewController?
// Adding
func add_ViewController() {
let controller = self.storyboard?.instantiateViewController(withIdentifier: "secondViewController")as! SecondViewController
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
self.secondViewController = controller
}
// Removing
func remove_ViewController(secondViewController:SecondViewController?) {
if secondViewController != nil {
if self.view.subviews.contains(secondViewController!.view) {
secondViewController!.view.removeFromSuperview()
}
}
}
Thanks to Rob, Updated Swift 4.2 syntax
let controller:WalletView = self.storyboard!.instantiateViewController(withIdentifier: "MyView") as! WalletView
controller.view.frame = self.view.bounds
self.view.addSubview(controller.view)
self.addChild(controller)
controller.didMove(toParent: self)
func callForMenuView()
{
if(!isOpen)
{
isOpen = true
let menuVC : MenuViewController = self.storyboard!.instantiateViewController(withIdentifier: "menu") as! MenuViewController
self.view.addSubview(menuVC.view)
self.addChildViewController(menuVC)
menuVC.view.layoutIfNeeded()
menuVC.view.frame=CGRect(x: 0 - UIScreen.main.bounds.size.width, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
UIView.animate(withDuration: 0.3, animations: { () -> Void in
menuVC.view.frame=CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width-90, height: UIScreen.main.bounds.size.height);
}, completion:nil)
}else if(isOpen)
{
isOpen = false
let viewMenuBack : UIView = view.subviews.last!
UIView.animate(withDuration: 0.3, animations: { () -> Void in
var frameMenu : CGRect = viewMenuBack.frame
frameMenu.origin.x = -1 * UIScreen.main.bounds.size.width
viewMenuBack.frame = frameMenu
viewMenuBack.layoutIfNeeded()
viewMenuBack.backgroundColor = UIColor.clear
}, completion: { (finished) -> Void in
viewMenuBack.removeFromSuperview()
})
}
Please also check the official documentation on implementing a custom container view controller:
https://developer.apple.com/library/content/featuredarticles/ViewControllerPGforiPhoneOS/ImplementingaContainerViewController.html#//apple_ref/doc/uid/TP40007457-CH11-SW1
This documentation has much more detailed information for every instruction and also describes how to do add transitions.
Translated to Swift 3:
func cycleFromViewController(oldVC: UIViewController,
newVC: UIViewController) {
// Prepare the two view controllers for the change.
oldVC.willMove(toParentViewController: nil)
addChildViewController(newVC)
// Get the start frame of the new view controller and the end frame
// for the old view controller. Both rectangles are offscreen.r
newVC.view.frame = view.frame.offsetBy(dx: view.frame.width, dy: 0)
let endFrame = view.frame.offsetBy(dx: -view.frame.width, dy: 0)
// Queue up the transition animation.
self.transition(from: oldVC, to: newVC, duration: 0.25, animations: {
newVC.view.frame = oldVC.view.frame
oldVC.view.frame = endFrame
}) { (_: Bool) in
oldVC.removeFromParentViewController()
newVC.didMove(toParentViewController: self)
}
}
Swift 5.1
To Add:
let controller = storyboard?.instantiateViewController(withIdentifier: "MyViewControllerId")
addChild(controller!)
controller!.view.frame = self.containerView.bounds
self.containerView.addSubview((controller?.view)!)
controller?.didMove(toParent: self)
To remove:
self.containerView.subviews.forEach({$0.removeFromSuperview()})