I have created a UIViewController with a UITabbar in it.
I did not use UITabbarController because I wanted UITabbar on the top of the screen.
Upon clicking tab1, I want to present controller1 and on clicking tab2 I want to present controller 2. I don't want the tabbar to hide. I want to display the controller beneath the tabbar.
#interface MTLeaderFactoViewController () <UITabBarDelegate>
#property (weak, nonatomic) IBOutlet UITabBar *tabBar;
#end
#implementation MTLeaderFactoViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
if (item.tag == 0) {
NSLog(#"item tag 0");
} else {
NSLog(#"item tag 1");
}
}
#end
My questions:
1) didSelectItem method is not triggered even after using UITabbarDelegate
2) What is the most elegant way of displaying the controller when clicked on a button? I don't want to use segue as all the controllers are in different storyboards.
For now, I plan to do
Controller1 *fp = [Controller1 controllerStoryboard:STORYBOARD_COURSE];
[self addChildViewController:fp];
[self.view addSubview:fp.view];
[fp didMoveToParentViewController:self];
EDIT 1:
Controller1 *fp = [Controller1 controllerStoryboard:STORYBOARD_COURSE];
[self addChildViewController:fp];
[self.view addSubview:fp.view];
[fp didMoveToParentViewController:self];
Tried this but it hides the tab bar. I want to utilize the space beneath the tab bar to display the controller
What you need to do is have a basecontroller class which will contain a tabbar(programatically created) then you can achive the desired output heres a sample baseController that i created,
import UIKit
class BaseViewController: UIViewController,UITabBarDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let myTabBar = UITabBar()
myTabBar.frame = CGRect(x: 0, y: 60, width:self.view.frame.size.width, height: 50)
let one = UITabBarItem()
one.title = "one"
one.tag = 1
let two = UITabBarItem()
two.title = "two"
two.tag = 2
myTabBar.setItems([one,two], animated: false)
self.view.addSubview(myTabBar)
myTabBar.delegate = self
}
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
switch item.tag {
case 1:
let controller = storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
addChildViewController(controller!)
view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
break
case 2:
let controller = storyboard?.instantiateViewController(withIdentifier: "ViewController")
addChildViewController(controller!)
view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
break
default:
break
}
}
}
View Controller class :
import UIKit
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
SecondView Controller :
import UIKit
class SecondViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
I would suggest using UITabBarController instead of UIViewController.
Add UITabViewController in your StoryBoard name it FirstTabBarController.
Add child view controller to FirstTabBarController. In all your child view of add UITabbar. Connect the TabBar delegate to each of your child ViewController.
Hide the default TabBar in your default FirstTabBarController.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tabBar.isHidden = true;
[self setSelectedIndex:1];
}
Add action to TabBar in your child ViewController as
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
NSUInteger index = [[theTabBar items] indexOfObject:item];
NSLog(#"Tab index = %u", (int)indexO);
[self.navigationController.tabBarController setSelectedIndex:index];
}
I would prefer this method over manually adding adding or removing ViewController as subview, let the UITabBarController manage that for you. Do let me know if you have further queries.
Related
I want to display a 3D model like a slide show. When you click on next, the next one comes up does its animation for few seconds.... then next.... and this continues in tandem of changing UILabel text.
PressedNextbtn ---> (On ARSCNView) New 3D model displays ---> PressedNextbtn ---> (On ARSCNView) New 3D model displays --->
Also I would like to add a previous button so it can go back as well. Please check out the image below for further information if this is unclear.
So :
Starts with idle 3D model
User presses next
Changes the 3d model with inherent animation playing and laos changes UI LABEL
The above 3D model changes every time user presses next
Also an ability to go back to previous if necessary.
note : I have figured out the UILabel, now I need to run the 3D model slide show unto the system seen in the image. I hope this clarifies everything.
#IBAction func nextPressed(_ sender: Any) {
displayNext()
//DISPLAYs NEXT 3D model inherent ANIMATION from array
}
#IBAction func prevPressed(_ sender: Any) {
getPrevious()
//DISPLAY Previous 3D model with inherent ANIMATION
}
func displayNext() {
guard currentIndex < myArray.count else {
// timer.invalidate() //if you don't want to repeat showing the images you can invalidate the timer.
currentIndex = 0 // add this if you want to repeat showing the image
return
}
allArray.append(myArray[currentIndex])
sildeText.text = myArray[currentIndex]
currentIndex += 1
}
func getPrevious() {
if allArray.count > 1 {
allArray.removeLast()
sildeText.text = allArray.last
}
else {
sildeText.text = allArray.last
}
}
Please check the app structure out..the 3D model is not properly positioned yet.
There are two options to achieve this behavior,
Add UIPageviewController and in the content add your UILabel and 3D model and change the page index on 'Next' or 'Previous' button click.
Use Horizontal UIScrollView and change the frame along with UILabel and 3D model on 'Next' or 'Previous' button click.
I would prefer using UIPageviewController,
In ViewController.m,
import "ViewController.h"
#interface ViewController () <UIPageViewControllerDataSource, UIPageViewControllerDelegate>
#property (nonatomic,strong) UIPageViewController *PageViewController;
#property (nonatomic, strong) NSArray *arrPageTitles;
#property (nonatomic, strong) NSArray *arrPageDescription;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Create page view controller
self.PageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"PageViewController"];
self.PageViewController.dataSource = self;
self.PageViewController.delegate = self;
self.shouldShowPageController = YES;
[self addChildViewController:PageViewController];
[self.view addSubview:PageViewController.view];
[self.PageViewController didMoveToParentViewController:self];
}
#pragma mark - Page View Datasource Methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController{
NSUInteger index = ((PageContentVC *) viewController).pageIndex;
if (index == NSNotFound) {
return nil;
}
if (index == 0) {
return [self viewControllerAtIndex:([self.arrPageTitles count] - 1)];
}
index--;
return [self viewControllerAtIndex:index];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController{
NSUInteger index = ((PageContentVC *) viewController).pageIndex;
if (index == NSNotFound){
return nil;
}
index++;
if (index == [self.arrPageTitles count]){
return [self viewControllerAtIndex:0];
}
return [self viewControllerAtIndex:index];
}
#pragma mark - PageContent View Controller Method
- (PageContentVC *)viewControllerAtIndex:(NSUInteger)index{
if (([self.arrPageTitles count] == 0) || (index >= [self.arrPageTitles count])) {
return nil;
}
// Create a new view controller and pass suitable data.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"PageContentVC" bundle:[NSBundle mainBundle]];
PageContentVC *pageContentViewController = [storyboard instantiateViewControllerWithIdentifier:#"PageContent"];
pageContentViewController.title = self.arrPageImages[index];
pageContentViewController.description = self.arrPageTitles[index];
pageContentViewController.pageIndex = index;
return pageContentViewController;
}
#end
here, PageContentVC is the content view controller inside the UIPageviewController
Set data to object in viewDidLoad() method.
override func viewDidLoad()
{
super.viewDidLoad()
imageView.image = UIImage(named: strPhotoName)
lblTitle.text = strTitle
}
Implement UIPageViewController
Create two NSArray to store Image title and Image name
var arrPageTitle: NSArray = NSArray()
var arrPagePhoto: NSArray = NSArray()
Variable Initialisation
Initialise Array.
Set data source for PageViewController.
Set first page of PageViewController from viewDidLoad() method.
override func viewDidLoad() {
super.viewDidLoad()
arrPageTitle = ["This is The App Guruz", "This is Table Tennis 3D", "This is Hide Secrets"];
arrPagePhoto = ["1.jpg", "2.jpg", "3.jpg"];
self.dataSource = self
self.setViewControllers([getViewControllerAtIndex(0)] as [UIViewController], direction: UIPageViewControllerNavigationDirection.Forward, animated: false, completion: nil)
}
Implement Datasource Methods
viewControllerBeforeViewController()
Use this method to return to previous view. In this I have put a condition such that if it is first view then return nil otherwise return ViewController.
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController?
{
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if ((index == 0) || (index == NSNotFound))
{
return nil
}
index--;
return getViewControllerAtIndex(index)
}
viewControllerAfterViewController
Use this method to return next view. In this I have put a condition such that if it is last view then return nil otherwise return ViewController.
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController?
{
let pageContent: PageContentViewController = viewController as! PageContentViewController
var index = pageContent.pageIndex
if (index == NSNotFound)
{
return nil;
}
index++;
if (index == arrPageTitle.count)
{
return nil;
}
return getViewControllerAtIndex(index)
}
Create method for get new PageContentViewController
getViewControllerAtIndex
When user swipe using gesture then we have to instantiate ViewController from storyboard and assign all content data of ViewController and return it.
func getViewControllerAtIndex(index: NSInteger) -> PageContentViewController
{
// Create a new view controller and pass suitable data.
let pageContentViewController = self.storyboard?.instantiateViewControllerWithIdentifier("PageContentViewController") as! PageContentViewController
pageContentViewController.strTitle = "\(arrPageTitle[index])"
pageContentViewController.strPhotoName = "\(arrPagePhoto[index])"
pageContentViewController.pageIndex = index
return pageContentViewController
}
i have an iOS app with video player, when the video is playing (landscape, full screen) i would like to hide the home indicator on iPhone X. I have try with
if (#available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
and also
-(BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
but no luck. Does anyone have any idea?
When implementing a container view controller, override
childViewControllerForHomeIndicatorAutoHidden() method if you want
one your child view controllers to determine whether to display the
visual indicator. If you do, the system calls the
prefersHomeIndicatorAutoHidden() method of the returned view
controller. If the method returns nil, the system calls the
prefersHomeIndicatorAutoHidden() method of the current view controller
So if you are using childViewController then need to implement childViewControllerForHomeIndicatorAutoHidden
as -
Swift
extension UINavigationController {
open override func childViewControllerForHomeIndicatorAutoHidden() -> UIViewController? {
return DemoViewController.loadFromNib()
}
}
//DemoViewController is childViewController
class DemoViewController: UIViewController {
static func loadFromNib() -> DemoViewController{
let storyBoardInst = UIStoryboard(name: "Main", bundle: nil)
return storyBoardInst.instantiateViewController(withIdentifier: "DemoViewController") as! DemoViewController
}
override func prefersHomeIndicatorAutoHidden() -> Bool {
return true
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
view.backgroundColor = .red
if #available(iOS 11.0, *) {
//Notifies UIKit that your view controller updated its preference regarding the visual indicator
setNeedsUpdateOfHomeIndicatorAutoHidden()
}
}
}
Objective C-
#interface UINavigationController(custom)
#end
#implementation UINavigationController(custom)
-(UIViewController *)childViewControllerForHomeIndicatorAutoHidden{
return [self.storyboard instantiateViewControllerWithIdentifier:#"DemoViewController"];
}
#end
//DemoViewController is childViewController
#interface DemoViewController ()
#end
#implementation DemoViewController
-(BOOL)prefersHomeIndicatorAutoHidden{
return YES;
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:YES];
self.view.backgroundColor = [UIColor redColor];
//Notifies UIKit that your view controller updated its preference
// regarding the visual indicator
if (#available(iOS 11.0, *)) {
[self setNeedsUpdateOfHomeIndicatorAutoHidden];
}
}
Output -
I want switch the sub view controller from the segment control.
There is vc1 and vc2 in the storyboard, and there is segment control on the main vc's navigation controller bar.
I want to add the vc1 and vc2 on the main vc, how to switch the sub vc use the segment controller?
How to do with that?
Follow below steps.
Add VC1 & VC2 as a childVC of mainVC.
On segment 1 selection VC1.view.hidden = false & vc2.view.hidden = true
On segment 2 selection VC2.view.hidden = false & vc1.view.hidden = true
take reference
How-to-add-childVC
How-tobind-segment-control-action
Code work
#IBAction func indexChanged(_ sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex
{
case 0:
vc1.view.hidden = false
vc2.view.hidden = true
case 1:
vc2.view.hidden = false
vc1.view.hidden = true
default:
break
}
}
The accepted answer is obviously correct, but I honestly prefer using container view for each tab in UISegmentedControl. It that way the logic related with each view is separated in different view controller. You can achieve this in that way:
class TopViewController: UIViewController {
#IBOutlet weak var firstContainerView: UIView!
#IBOutlet weak var secondContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
firstContainerView.alpha = 1.0
secondContainerView.alpha = 0.0
}
#IBAction func didChangeIndex(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
firstContainerView.alpha = 1.0
secondContainerView.alpha = 0.0
case 1:
firstContainerView.alpha = 0.0
secondContainerView.alpha = 1.0
default:
break
}
}
}
If you would like to access properties of FirstViewController or SecondViewController, you can implement prepare(for segue: UIStoryboardSegue, sender: Any?) method.
You should add vc, and the vc.view to the Main ViewController:
When you select the Segmented Control, you can hide the sub viewcontroller's view like below:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
_vc1 = [sb instantiateViewControllerWithIdentifier:#"ViewController1"];
_vc2 = [sb instantiateViewControllerWithIdentifier:#"ViewController2"];
[self addChildViewController:_vc1];
[self addChildViewController:_vc2];
[self.view addSubview:_vc1.view];
[self.view addSubview:_vc2.view];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)segAction:(UISegmentedControl *)sender {
if (1 == sender.selectedSegmentIndex) {
NSLog(#"1");
_vc1.view.hidden = YES;
_vc2.view.hidden = NO;
}else {
NSLog(#"%ld", sender.selectedSegmentIndex);
_vc2.view.hidden = YES;
_vc1.view.hidden = NO;
}
}
Is there a way to present a view controller modally without knowing what the visible view controller view is? Basically sort of like you would show an alert view at any points in time.
I would like to be able to do something like:
MyViewController *myVC = [[MyViewController alloc] init];
[myVC showModally];
I'd like to be able to call this from anywhere in the app, and have it appear on top. I don't want to care about what the current view controller is.
I plan to use this to show a login prompt. I don't want to use an alert view, and I also don't want to have login presentation code throughout the app.
Any thoughts on this? Or is there maybe a better way to achieve this? Should I just implement my own mechanism and just place a view on top of the window?
Well, you can follow the chain.
Start at [UIApplication sharedApplication].delegate.window.rootViewController.
At each view controller perform the following series of test.
If [viewController isKindOfClass:[UINavigationController class]], then proceed to [(UINavigationController *)viewController topViewController].
If [viewController isKindOfClass:[UITabBarController class]], then proceed to [(UITabBarController *)viewController selectedViewController].
If [viewController presentedViewController], then proceed to [viewController presentedViewController].
My solution in Swift (inspired by the gist of MartinMoizard)
extension UIViewController {
func presentViewControllerFromVisibleViewController(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
if let navigationController = self as? UINavigationController {
navigationController.topViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
} else if let tabBarController = self as? UITabBarController {
tabBarController.selectedViewController?.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
} else if let presentedViewController = presentedViewController {
presentedViewController.presentViewControllerFromVisibleViewController(viewControllerToPresent, animated: flag, completion: completion)
} else {
present(viewControllerToPresent, animated: flag, completion: completion)
}
}
}
This solution gives you the top most view controller so that you can handle any special conditions before presenting from it. For example, maybe you want to present your view controller only if the top most view controller isn't a specific view controller.
extension UIApplication {
/// The top most view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.visibleViewController
}
}
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else {
return self
}
}
}
With this you can present your view controller from anywhere without needing to know what the top most view controller is
UIApplication.topMostViewController?.present(viewController, animated: true, completion: nil)
Or present your view controller only if the top most view controller isn't a specific view controller
if let topVC = UIApplication.topMostViewController, !(topVC is FullScreenAlertVC) {
topVC.present(viewController, animated: true, completion: nil)
}
One thing to note is that if there's a UIAlertController currently being displayed, UIApplication.topMostViewController will return a UIAlertController. Presenting on top of a UIAlertController has weird behavior and should be avoided. As such, you should either manually check that !(UIApplication.topMostViewController is UIAlertController) before presenting, or add an else if case to return nil if self is UIAlertController
extension UIViewController {
/// The visible view controller from a given view controller
var visibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.visibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.visibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.visibleViewController
} else if self is UIAlertController {
return nil
} else {
return self
}
}
}
You could have this code implemented in your app delegate:
AppDelegate.m
-(void)presentViewControllerFromVisibleController:(UIViewController *)toPresent
{
UIViewController *vc = self.window.rootViewController;
[vc presentViewController:toPresent animated:YES];
}
AppDelegate.h
-(void)presentViewControllerFromVisibleViewController:(UIViewController *)toPresent;
From Wherever
#import "AppDelegate.h"
...
AppDelegate *delegate = [UIApplication sharedApplication].delegate;
[delegate presentViewControllerFromVisibleViewController:myViewControllerToPresent];
In your delegate, you're getting the rootViewController of the window. This will always be visible- it's the 'parent' controller of everything.
I don't think you necessarily need to know which view controller is visible. You can get to the keyWindow of the application and add your modal view controller's view to the top of the list of views. Then you can make it work like the UIAlertView.
Interface file: MyModalViewController.h
#import <UIKit/UIKit.h>
#interface MyModalViewController : UIViewController
- (void) show;
#end
Implementation file: MyModalViewController.m
#import "MyModalViewController.h"
#implementation MyModalViewController
- (void) show {
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
// Configure the frame of your modal's view.
[window addSubview: self.view];
}
#end
I am trying something very simple but somehow I can't get it to work. All I try to do is switching between 2 View Controllers using an UISegmentedControl as you can see it for example in the App Store application in the Highlights tab.
I am using iOS5 and Storyboards.
Here's my Storyboad line up:
So I have a root View Controller and two UITableViews - This 2 TableViews I want to switch.
Here's how the implementation file looks like
#import "SegmentedLocationViewController.h"
#import "PastEventsLocationViewController.h"
#import "FutureEventsLocationViewController.h"
#interface SegmentedLocationViewController()
#property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedControl;
#property (strong, nonatomic) NSArray *viewControllers;
#end
#implementation SegmentedLocationViewController
#synthesize segmentedControl = _segmentedControl;
#synthesize viewControllers = _viewControllers;
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl*)segmentedControl
{
NSLog(#"index: %d", segmentedControl.selectedSegmentIndex);
}
- (void)setupViewControllers
{
PastEventsLocationViewController *pastEventsLocationViewController = [[PastEventsLocationViewController alloc] initWithStyle:UITableViewStylePlain];
FutureEventsLocationViewController *futureEventsLocationViewController = [[FutureEventsLocationViewController alloc] initWithStyle:UITableViewStylePlain];
self.viewControllers = [NSArray arrayWithObjects:pastEventsLocationViewController, futureEventsLocationViewController, nil];
}
- (void)setupUI
{
[self.segmentedControl addTarget:self action:#selector(indexDidChangeForSegmentedControl:) forControlEvents:UIControlEventValueChanged];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViewControllers];
[self setupUI];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
I can trigger the switch event and can log the currently selected index. But I don't have any idea where to go from here.
Maybe someone can turn my attention towards a certain direction...?
This code works pretty well for your purpose, I use it for one of my new apps.
It uses the new UIViewController containment APIs that allow UIViewControllers inside your own UIViewControllers without the hassles of manually forwarding stuff like viewDidAppear:
- (void)viewDidLoad {
[super viewDidLoad];
// add viewController so you can switch them later.
UIViewController *vc = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:vc];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
self.currentViewController = vc;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *vc = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:vc];
[self transitionFromViewController:self.currentViewController toViewController:vc duration:0.5 options:UIViewAnimationOptionTransitionFlipFromBottom animations:^{
[self.currentViewController.view removeFromSuperview];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
} completion:^(BOOL finished) {
[vc didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = vc;
}];
self.navigationItem.title = vc.title;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *vc;
switch (index) {
case 0:
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"FooViewController"];
break;
case 1:
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"BarViewController"];
break;
}
return vc;
}
I got this stuff from chapter 22 of Ray Wenderlichs book iOS5 by tutorial.
Unfortunately I don't have a public link to a tutorial. But there is a WWDC 2011 video titled "Implementing UIViewController Containment"
EDIT
self.typeSegmentedControl is outlet for your UISegmentedControl
self.contentView is outlet for your container view
self.currentViewController is just a property that we're using to store our currently used UIViewController
It's Matthias Bauch solution, but thought of sharing it in Swift anyway!
Edit:
Adding a link to a Swift 2.0 ready made demo app.
https://github.com/ahmed-abdurrahman/taby-segmented-control
var currentViewController: UIViewController?
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBAction func switchHappeningTabs(sender: UISegmentedControl) {
if let vc = viewControllerForSelectedSegmentIndex(sender.selectedSegmentIndex) {
self.addChildViewController(vc)
self.transitionFromViewController(self.currentViewController!, toViewController: vc, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
self.currentViewController!.view.removeFromSuperview()
vc.view.frame = self.contentView.bounds
self.contentView.addSubview(vc.view)
}, completion: { finished in
vc.didMoveToParentViewController(self)
self.currentViewController!.removeFromParentViewController()
self.currentViewController = vc
}
)
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let vc = self.viewControllerForSelectedSegmentIndex(self.segmentedControl.selectedSegmentIndex) {
self.addChildViewController(vc)
self.contentView.addSubview(vc.view)
self.currentViewController = vc
}
}
func viewControllerForSelectedSegmentIndex(index: Int) -> UIViewController? {
var vc: UIViewController?
switch index {
case 0:
vc = self.storyboard?.instantiateViewControllerWithIdentifier("FooViewController") as? UIViewController
case 1:
vc = self.storyboard?.instantiateViewControllerWithIdentifier("BarViewController") as? UIViewController
default:
return nil
}
return vc
}
For someone want to implement the same in swift
import UIKit
class HomeController: UIViewController {
var currentViewController:UIViewController?
#IBOutlet weak var homeController: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let initialController:UIViewController = self.viewControllerForSegmentedIndex(0)
self.addChildViewController(initialController)
initialController.view.frame = self.homeController.bounds
self.homeController.addSubview(initialController.view)
self.currentViewController = initialController
}
#IBAction func segmentChanged(sender: UISegmentedControl) {
let viewCOntroller:UIViewController = viewControllerForSegmentedIndex(sender.selectedSegmentIndex)
self.addChildViewController(viewCOntroller)
self.transitionFromViewController(self.currentViewController!, toViewController: viewCOntroller, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromBottom, animations: {
self.currentViewController?.view.removeFromSuperview()
viewCOntroller.view.frame = self.homeController.bounds
self.homeController.addSubview(viewCOntroller.view)
}, completion:{ finished in
viewCOntroller.didMoveToParentViewController(self)
self.currentViewController?.removeFromParentViewController()
self.currentViewController = viewCOntroller
})
}
func viewControllerForSegmentedIndex(index:Int) -> UIViewController {
var viewController:UIViewController?
switch index {
case 0:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForFirstController")
break
case 1:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForSecondController")
break
case 2:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForThirdController")
break
default:
break
}
return viewController!
}
}
Storyboard view
A is root view controller, and B, C is sub view controller.
When you click segment, add sub view controller.
result
design
code
import UIKit
class SegmentViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
var leftViewController: LeftViewController!
var rightViewController: RightViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let sb = storyboard {
leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController
switchViewController(from: nil, to: leftViewController)
} else {
print("storyboard is nil")
}
}
func switchViewController(from fromVC: UIViewController?, to toVC: UIViewController?) {
if let from = fromVC {
from.willMoveToParentViewController(nil)
from.view.removeFromSuperview()
from.removeFromParentViewController()
} else {
print("fromVC is nil")
}
if let to = toVC {
self.addChildViewController(to)
to.view.frame = CGRectMake(0, 0, containerView.frame.width, containerView.frame.height)
self.containerView.insertSubview(to.view, atIndex: 0)
to.didMoveToParentViewController(self)
} else {
print("toVC is nil")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
removeViewController()
}
func removeViewController() {
if let leftVC = leftViewController {
if let _ = leftVC.parentViewController {
print("leftVC is using")
} else {
print("set leftVC = nil")
leftViewController = nil
}
}
if let rightVC = rightViewController {
if let _ = rightVC.parentViewController {
print("rightVC is using")
} else {
print("set rightVC = nil")
rightViewController = nil
}
}
}
#IBAction func onSegmentValueChanged(sender: UISegmentedControl) {
UIView.beginAnimations("xxx", context: nil)
UIView.setAnimationDuration(0.4)
UIView.setAnimationCurve(.EaseInOut)
switch sender.selectedSegmentIndex {
case 0:
UIView.setAnimationTransition(.FlipFromRight, forView: self.containerView, cache: true)
if let leftVC = leftViewController {
switchViewController(from: rightViewController, to: leftVC)
} else {
if let sb = storyboard {
leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController
switchViewController(from: rightViewController, to: leftViewController)
} else {
print("storyboard is nil")
}
}
default:
UIView.setAnimationTransition(.FlipFromLeft, forView: self.containerView, cache: true)
if let rightVC = rightViewController {
switchViewController(from: leftViewController, to: rightVC)
} else {
if let sb = storyboard {
rightViewController = sb.instantiateViewControllerWithIdentifier("rightViewController") as! RightViewController
switchViewController(from: leftViewController, to: rightViewController)
} else {
print("storyboard is nil")
}
}
}
UIView.commitAnimations()
}
}
You could embed your initial view controller in a navigation controller. Do that by selecting the view controller in the storyboard, then Editor->Embed in->Navigation Controller.
In your indexDidChangeForSegmentedControl: method you simply push the corresponding view controller to the navigation stack:
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl*)segmentedControl
{
[self.navigationController pushViewController:[self.viewControllers objectAtIndex:[segmentedControl.selectedIndex]] animated:YES];
}
But your approach makes not too much sense at all when you are using storyboards.
I don't know whether you can wire up a single segmented control button to a view controller using segues. Just try it.
I ended up using a navigation controller to achieve a similar functionality.
import UIKit
class BaseViewController: UIViewController {
var currentSegmentIndex = 0
#IBOutlet weak var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.selectedSegmentIndex = currentSegmentIndex
}
#IBAction func segmentedControlChanged(_ sender: Any) {
let idx = segmentedControl.selectedSegmentIndex
let storyboard = UIStoryboard(name: "Main", bundle: nil)
switch idx {
case 0:
let vc = storyboard.instantiateViewController(
withIdentifier: "Foo") as! FooViewController
vc.currentSegmentIndex = 0
navigationController?.viewControllers = [vc]
break
case 1:
let vc = storyboard.instantiateViewController(
withIdentifier: "Bar") as! BarViewController
vc.currentSegmentIndex = 1
navigationController?.viewControllers = [vc]
break
default:
break
}
}
}