How to present a semi-transparent (half-cut) viewcontroller in iOS? - ios

I use the following code to present a viewcontroller.
My problem is: After the animation completes, the transparent main background becomes opaque black.
How can I fix this and make it stay as clearColor?
UIViewController *menuViewController=[[UIViewController alloc]init];
menuViewController.view.backgroundColor=[UIColor clearColor];
menuViewController.view.tintColor=[UIColor clearColor];
menuViewController.view.opaque=NO;
UIView *menuView=[[UIView alloc]initWithFrame:CGRectMake(0,[UIScreen mainScreen].bounds.size.height-200,320,200)];
menuView.backgroundColor=[UIColor redColor];
[menuViewController.view addSubview:menuView];
[self presentViewController:menuViewController animated:YES completion:nil];
update: I am trying to see the contents of "self" (the presenter viewcontroller's view).

Update for iOS 15
Apple has introduced a new API, UISheetPresentationController, which makes it rather trivial to achieve a half-sized (.medium()) sheet presentation. If you are after something more custom, then the original answer is what you need.
let vc = UIViewController()
if let sheet = vc.presentationController as? UISheetPresentationController {
sheet.detents = [.medium()]
}
self.present(vc, animated: true, completion: nil)
Original Answer
You can present a view controller, and still have the original view controller visible underneath, like a form, in iOS 7. To do so, you will need to do two things:
Set the modal presentation style to custom:
viewControllerToPresent.modalPresentationStyle = UIModalPresentationCustom;
Set the transitioning delegate:
viewControllerToPresent.transitioningDelegate = self;
In this case, we have set the delegate to self, but it can be another object. The delegate needs to implement the two required methods of the protocol, possible like so:
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
semiModalAnimatedTransition.presenting = YES;
return semiModalAnimatedTransition;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
SemiModalAnimatedTransition *semiModalAnimatedTransition = [[SemiModalAnimatedTransition alloc] init];
return semiModalAnimatedTransition;
}
At this point you may be thinking, where did that SemiModalAnimatedTransition class come from. Well, it is a custom implementation adopted from teehan+lax's blog.
Here is the class's header:
#interface SemiModalAnimatedTransition : NSObject <UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign) BOOL presenting;
#end
And the implementation:
#import "SemiModalAnimatedTransition.h"
#implementation SemiModalAnimatedTransition
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return self.presenting ? 0.6 : 0.3;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGRect endFrame = fromViewController.view.bounds;
if (self.presenting) {
fromViewController.view.userInteractionEnabled = NO;
[transitionContext.containerView addSubview:fromViewController.view];
[transitionContext.containerView addSubview:toViewController.view];
CGRect startFrame = endFrame;
startFrame.origin.y = endFrame.size.height;
toViewController.view.frame = startFrame;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
fromViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed;
toViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
else {
toViewController.view.userInteractionEnabled = YES;
[transitionContext.containerView addSubview:toViewController.view];
[transitionContext.containerView addSubview:fromViewController.view];
endFrame.origin.y = endFrame.size.height;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toViewController.view.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic;
fromViewController.view.frame = endFrame;
} completion:^(BOOL finished) {
[transitionContext completeTransition:YES];
}];
}
}
#end
Not the most straightforward solution, but avoids hacks and works well. The custom transition is required because by default iOS will remove the first view controller at the end of the transition.
Update for iOS 8
For iOS 8, once again the landscape has changed. All you need to do is use the new presentation style .OverCurrentContext, ie:
viewControllerToPresent.modalPresentationStyle = UIModalPresentationOverCurrentContext;

Update
In most cases, you're going to want to follow the guidelines from Ric's answer, below. As he mentions, menuViewController.modalPresentationStyle = .overCurrentContext is the simplest modern way to keep the presenting view controller visible.
I'm preserving this answer because it provided the most direct solution to the OPs problem, where they already had a view managed by the current view controller and were just looking for a way to present it, and because it explains the actual cause problem.
As mentioned in the comments, this isn't a transparency problem (otherwise you would expect the background to become white). When the presentViewController:animated:completion: animation completes, the presenting view controller is actually removed from the visual stack. The black you're seeing is the window's background color.
Since you appear to just be using menuViewController as a host for menuView to simplify the animation, you could consider skipping menuViewController, adding menuView to your existing view controllers view hierarchy, and animate it yourself.

This is a fairly simple problem to solve. Rather than creating a custom view transition, you just need to set the modalPresentationStyle for the view controller being presented.
Also, you should set the background color (and alpha value) for the view controller being presented, in the storyboard / via code.
CustomViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.6)
}
}
In the IBAction component of presenting view controller -
let vc = storyboard?.instantiateViewControllerWithIdentifier("customViewController") as! CustomViewController
vc.modalPresentationStyle = UIModalPresentationStyle.Custom
presentViewController(vc, animated: true, completion: nil)

You should use a property modalPresentationStyle available since iOS 3.2.
For example:
presenterViewController.modalPresentationStyle = UIModalPresentationCurrentContext;
[presenterViewController presentViewController:loginViewController animated:YES completion:NULL];

Try making top view transparent and add a another view below your desired view and make that view's background color black and set alpha 0.5 or whatever opacity level you like.

It's quite an old post and thanks to Ric's answer, it still works well, but few fixes are required to run it on iOS 14. I suppose it works fine on lower versions of iOS, but I hadn't a chance to test it, since my deployment target is iOS 14.
OK, here is an updated solution in Swift:
final class SemiTransparentPopupAnimator: NSObject, UIViewControllerAnimatedTransitioning {
var presenting = false
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return presenting ? 0.4 : 0.2
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard
let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
else {
return
}
var endFrame = fromVC.view.bounds
if presenting {
fromVC.view.isUserInteractionEnabled = false
transitionContext.containerView.addSubview(toVC.view)
var startFrame = endFrame
startFrame.origin.y = endFrame.size.height
toVC.view.frame = startFrame
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
fromVC.view.tintAdjustmentMode = .dimmed
toVC.view.frame = endFrame
} completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
} else {
toVC.view.isUserInteractionEnabled = true
endFrame.origin.y = endFrame.size.height
UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
toVC.view.tintAdjustmentMode = .automatic
fromVC.view.frame = endFrame
} completion: { _ in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
}

Related

custom Segue correct way

Currently I try to work on a custom segue it should look like:
The [Destination View] should be behind the [Source View], this [Source View] should animate from 100% down to 0.1% and then remove, in the animation time the [Destination View] should also be there in background.
So you see the [Source View] become small in front of the [Destination View] and get removed.
This is my code:
import UIKit
class CustomSegueFromBigtoSmall: UIStoryboardSegue {
override func perform() {
let sourceVC = self.sourceViewController
let destinationVC = self.destinationViewController
sourceVC.view.addSubview(destinationVC.view)
UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
sourceVC.view.transform = CGAffineTransformMakeScale(0.1, 0.1)
}){ (finished) -> Void in
destinationVC.view.removeFromSuperview()
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(0.001 * Double(NSEC_PER_SEC)))
dispatch_after(time, dispatch_get_main_queue(), { () -> Void in
sourceVC.presentViewController(destinationVC, animated: false, completion: nil)
})
}
}
}
right now I see my [Source View], that instantly becomes my [Destination View]. The [Destination View] become small in front of a black background. Once it is "small" it simply appears as full screen.
The way I would do this in iOS 9 is to make this a presentation (modal) segue with a custom segue implementation. Your custom segue then simply calls super.perform() to do the actual presentation. But first, the segue sets itself as the destination's transitionDelegate and sets its presentation style to Custom. Now you're just doing an ordinary custom transition animation, with your own transitioning delegate and your own UIPresentationController, and you can do whatever you want, in good order.

How to avoid animateTransition(transitionContext) protocol method(in navigationController push methods) from removing previous ViewController's view?

i have to push a viewController that has a dimming view with an alpha of 0.5. Because of this, the ViewController's view has to show the previous controller's view behind this dimming background. The problem is that i'm using a navigationController that uses a UIViewControllerAnimatedTransitioning protocol to customize the animation. By default, after pushing the new viewController onto the stack, the navigationController automatically removes the previous view. So, how to keep the previous view after completing this transition, is this possible?
NOTES: i don't want to just add the controller's view to the navigationController(This gave me strange behaviors in the navigation functionality), and i do really need to push it in this way, so i can continue using the application code pattern.
CODE:
func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
if isPresenting {
let ContainerView = transitionContext.containerView()
if let PresentedController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) {
if let PresentedView = transitionContext.viewForKey(UITransitionContextToViewKey) {
PresentedView.alpha = 0
PresentedView.frame = transitionContext.finalFrameForViewController(PresentedController)
ContainerView.addSubview(PresentedView)
// i've also tried to add the fromView in the containerView.
UIView.animateWithDuration(0.4, animations: {
PresentedView.alpha = 1
}) {
Completion in
transitionContext.completeTransition(Completion)
}
}
}
} else {
// dismiss code...
}
}
Thanks for your patience.
I had a similar situation a few weeks ago. In my case, I was presenting my view controller modally. The way I got around the problem was to take a snapshot of the fromViewController's view and use it as background for toViewController's view.
To capture snapshot:
UIImage *snapshotImage = nil;
UIGraphicsBeginImageContextWithOptions(self.tabBarController.view.frame.size, NO, 0.0);
if ([self.tabBarController.view drawViewHierarchyInRect:self.tabBarController.view.bounds afterScreenUpdates:YES]) {
snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
You can capture transitionContext.containerView for this. Pass this image to your new VC and set it as the background to that VC's view as:
self.view.backgroundColor = [UIColor colorWithPatternImage:self.bgImage];
P.S. I am not fluent in Swift so you will have to convert the code to swift yourself but hopefully this gives you a decent starting point

Adding a view controller as a subview in another view controller

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()})

Presented view controller disappears after animation using custom UIViewController animations

I've looked around for an answer for this and spent the last two hours pulling my hair out to no end.
I'm implementing a very basic custom view controller transition animation, which simply zooms in on the presenting view controller and grows in the presented view controller. It adds a fade effect (0 to 1 alpha and visa versa).
It works fine when presenting the view controller, however when dismissing, it brings the presenting view controller back in all the way to fill the screen, but then it inexplicably disappears. I'm not doing anything after these animations to alter the alpha or the hidden values, it's pretty much a fresh project. I've been developing iOS applications for 3 years so I suspect this may be a bug, unless someone can find out where I'm going wrong.
class FadeAndGrowAnimationController : NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
func animationControllerForPresentedController(presented: UIViewController!, presentingController presenting: UIViewController!, sourceController source: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
return self
}
func animationControllerForDismissedController(dismissed: UIViewController!) -> UIViewControllerAnimatedTransitioning! {
return self
}
func transitionDuration(transitionContext: UIViewControllerContextTransitioning!) -> NSTimeInterval {
return 2
}
func animateTransition(transitionContext: UIViewControllerContextTransitioning!) {
let fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as UIViewController
let toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as UIViewController
toViewController.view.transform = CGAffineTransformMakeScale(0.5, 0.5)
toViewController.view.alpha = 0
transitionContext.containerView().addSubview(fromViewController.view)
transitionContext.containerView().addSubview(toViewController.view)
transitionContext.containerView().bringSubviewToFront(toViewController.view)
UIView.animateWithDuration(self.transitionDuration(transitionContext), animations: {
fromViewController.view.transform = CGAffineTransformScale(fromViewController.view.transform, 2, 2)
fromViewController.view.alpha = 1
toViewController.view.transform = CGAffineTransformMakeScale(1, 1)
toViewController.view.alpha = 1
}, completion: { finished in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled())
})
}
}
And the code to present:
let targetViewController = self.storyboard.instantiateViewControllerWithIdentifier("Level1ViewController") as Level1ViewController
let td = FadeAndGrowAnimationController()
targetViewController.transitioningDelegate = td
targetViewController.modalPresentationStyle = .Custom
self.presentViewController(targetViewController, animated: true, completion: nil)
As you can see, a fairly basic animation. Am I missing something here? Like I said, it presents perfectly fine, then dismisses 99.99% perfectly fine, yet the view controller underneath after the dismissal is inexplicably removed. The iPad shows a blank screen - totally black - after this happens.
This seems to be an iOS8 bug. I found a solution but it is ghetto. After the transition when a view should be on-screen but isn't, it needs to be added back to the window like this:
BOOL canceled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:!canceled];
if (!canceled)
{
[[UIApplication sharedApplication].keyWindow addSubview: toViewController.view];
}
You might need to play around with which view you add back to the window, whether to do it in canceled or !canceled, and perhaps making sure to only do it on dismissal and not presentation.
Sources: Container view disappearing on completeTransition:
http://joystate.wordpress.com/2014/09/02/ios8-and-custom-uiviewcontrollers-transitions/
I was having the same problem when dismissing a content view controller. My app has this parent view controller showing a child view controller. then when a subview in the child is tapped, it shows another vc (which I am calling the content vc)
My problem is that, when dismissing the contentVC, it should go to child VC but as soon as my custom transition finishes, childVC suddenly disappears, showing the parent VC instead.
What I did to solve this issue is to
change the .modalPresentationStyle of the childVC presented by parentVC from the default .automatic to .fullscreen.
Then changed the .modalPresentationStyle of contentVC to .fullscreen as well.
This solves the issue. but it won't show your child VC as a card sheet (when using .overCurrentContext or automatic) which is new in iOS 13.
had the same problem ios 8.1
my swift code after completeTransition
if let window = UIApplication.sharedApplication().keyWindow {
if let viewController = window.rootViewController {
window.addSubview(viewController.view)
}
}

How do I use a UISegmentedControl to switch views?

I'm trying to figure out how to use the different states of a UISegmentedControl to switch views, similar to how Apple does it in the App Store when switiching between 'Top Paid' and 'Top Free'.
The simplest approach is to have two views that you can toggle their visibility to indicate which view has been selected. Here is some sample code on how it can be done, definitely not an optimized way to handle the views but just to demonstrate how you can use the UISegmentControl to toggle the visible view:
- (IBAction)segmentSwitch:(id)sender {
UISegmentedControl *segmentedControl = (UISegmentedControl *) sender;
NSInteger selectedSegment = segmentedControl.selectedSegmentIndex;
if (selectedSegment == 0) {
//toggle the correct view to be visible
[firstView setHidden:NO];
[secondView setHidden:YES];
}
else{
//toggle the correct view to be visible
[firstView setHidden:YES];
[secondView setHidden:NO];
}
}
You can of course further re-factor the code to hide/show the right view.
In my case my views are quite complex and I cannot just change the hidden property of different views because it would take up too much memory.
I've tried several solutions and non of them worked for me, or performed erratically, specially with the titleView of the navBar not always showing the segmentedControl when pushing/popping views.
I found this blog post about the issue that explains how to do it in the proper way. Seems he had the aid of Apple engineers at WWDC'2010 to come up with this solution.
http://redartisan.com/2010/6/27/uisegmented-control-view-switching-revisited
The solution in this link is hands down the best solution I've found about the issue so far. With a little bit of adjustment it also worked fine with a tabBar at the bottom
Or if its a table, you can reload the table and in cellForRowAtIndex, populate the table from different data sources based on the segment option selected.
One idea is to have the view with the segmented controls have a container view that you fill with the different subviews (add as a sole subview of the container view when the segments are toggled). You can even have separate view controllers for those subviews, though you have to forward on important methods like "viewWillAppear" and "viewWillDisappear" if you need them (and they will have to be told what navigation controller they are under).
Generally that works pretty well because you can lay out the main view with container in IB, and the subviews will fill whatever space the container lets them have (make sure your autoresize masks are set up properly).
Try using SNFSegmentedViewController, an open-source component that does exactly what you're looking for with a setup like UITabBarController.
From the answer of #Ronnie Liew, I create this:
//
// ViewController.m
// ResearchSegmentedView
//
// Created by Ta Quoc Viet on 5/1/14.
// Copyright (c) 2014 Ta Quoc Viet. All rights reserved.
//
#define SIZE_OF_SEGMENT 56
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize theSegmentControl;
UIView *firstView;
UIView *secondView;
CGRect leftRect;
CGRect centerRect;
CGRect rightRect;
- (void)viewDidLoad
{
[super viewDidLoad];
leftRect = CGRectMake(-self.view.frame.size.width, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);
centerRect = CGRectMake(0, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);
rightRect = CGRectMake(self.view.frame.size.width, SIZE_OF_SEGMENT, self.view.frame.size.width, self.view.frame.size.height-SIZE_OF_SEGMENT);
firstView = [[UIView alloc] initWithFrame:centerRect];
[firstView setBackgroundColor:[UIColor orangeColor]];
secondView = [[UIView alloc] initWithFrame:rightRect];
[secondView setBackgroundColor:[UIColor greenColor]];
[self.view addSubview:firstView];
[self.view addSubview:secondView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)segmentSwitch:(UISegmentedControl*)sender {
NSInteger selectedSegment = sender.selectedSegmentIndex;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.2];
if (selectedSegment == 0) {
//toggle the correct view to be visible
firstView.frame = centerRect;
secondView.frame = rightRect;
}
else{
//toggle the correct view to be visible
firstView.frame = leftRect;
secondView.frame = centerRect;
}
[UIView commitAnimations];
}
#end
Assign .H in
UISegmentedControl *lblSegChange;
- (IBAction)segValChange:(UISegmentedControl *) sender
Declare .M
- (IBAction)segValChange:(UISegmentedControl *) sender
{
if(sender.selectedSegmentIndex==0)
{
viewcontroller1 *View=[[viewcontroller alloc]init];
[self.navigationController pushViewController:view animated:YES];
}
else
{
viewcontroller2 *View2=[[viewcontroller2 alloc]init];
[self.navigationController pushViewController:view2 animated:YES];
}
}
Swift version:
The parent view controller is responsible for setting the size and position of the view of each child view controller. The view of the child view controller becomes part of the parent view controller's view hierarchy.
Define lazy properties:
private lazy var summaryViewController: SummaryViewController = {
// Load Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// Instantiate View Controller
var viewController = storyboard.instantiateViewController(withIdentifier: "SummaryViewController") as! SummaryViewController
// Add View Controller as Child View Controller
self.add(asChildViewController: viewController)
return viewController
}()
private lazy var sessionsViewController: SessionsViewController = {
// Load Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
// Instantiate View Controller
var viewController = storyboard.instantiateViewController(withIdentifier: "SessionsViewController") as! SessionsViewController
// Add View Controller as Child View Controller
self.add(asChildViewController: viewController)
return viewController
}()
Show/Hide Child View Controllers:
private 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)
}
private 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()
}
Manage SegmentedControl tapEvent
private func updateView() {
if segmentedControl.selectedSegmentIndex == 0 {
remove(asChildViewController: sessionsViewController)
add(asChildViewController: summaryViewController)
} else {
remove(asChildViewController: summaryViewController)
add(asChildViewController: sessionsViewController)
}
}
And of course you are able to use within your child view controller classes:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("Summary View Controller Will Appear")
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("Summary View Controller Will Disappear")
}
Reference:
https://cocoacasts.com/managing-view-controllers-with-container-view-controllers/
A quick Swift Version:
#IBAction func segmentControlValueChanged(_ sender: UISegmentedControl) {
if segmentControl.selectedSegmentIndex == 0 {
// do something
} else {
// do something else
}
}

Resources