I have the following layout.
Green and Orange views are extra views added to the view controller.
I want to change the contained view controllers view to be changed according to the button user clicked.
I have Answer Using UIContainer dynamically show two ViewControllers in ios,
I have Create BaseViewController and Two viewControllers(GreenViewController and OrangeViewController), let seen below image are
And baseView inserted two UIButton(Green and Orange button), UIContainerView and the source is below,
BaseViewController.h file:
#property (weak, nonatomic) IBOutlet UIView *containView;
#property (weak, nonatomic) UIViewController *currentViewController;
BaseViewController.m file:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"GreenViewController"];
_currentViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addChildViewController:_currentViewController];
[self addSubview:_currentViewController.view toView:_containView];
}
- (void)addSubview:(UIView *)subView toView:(UIView*)parentView {
[parentView addSubview:subView];
NSDictionary * views = #{#"subView" : subView,};
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
}
- (void)cycleFromViewController:(UIViewController*) oldViewController
toViewController:(UIViewController*) newViewController {
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
[self addSubview:newViewController.view toView:self.containView];
[newViewController.view layoutIfNeeded];
// set starting state of the transition
newViewController.view.alpha = 0;
[UIView animateWithDuration:0.5
animations:^{
newViewController.view.alpha = 1;
oldViewController.view.alpha = 0;
}
completion:^(BOOL finished) {
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
}
Green Button Action is below
- (IBAction)greenViewAction:(id)sender {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"GreenViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
Orange Button Action is below
- (IBAction)orangeViewAction:(id)sender {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"OrangeViewController"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
its working for me, see the output below,
hope its helpful
Related
I am having a view controller with UISegment in it.
On clicking the segment controller instead of navigating to UIVIew I am using UIVIEWcontrolers.
Here is my code
.h file
#property (weak, nonatomic) IBOutlet UIView *containerView;
#property (weak, nonatomic) IBOutlet UISegmentedControl *segmenter;
- (IBAction)showComponent:(UISegmentedControl *)sender;
#property (weak, nonatomic) UIViewController *currentViewController;
-(void)loadViewFromData;
.m file
- (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appD = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if ([[appD.defaults objectForKey:#"segment"] isEqualToString:#"index0"]) {
self.currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr1"];
self.segmenter.selectedSegmentIndex = 0;
[self.segmenter sendActionsForControlEvents:UIControlEventValueChanged];
}else{
self.currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr2"];
self.segmenter.selectedSegmentIndex = 1;
[self.segmenter sendActionsForControlEvents:UIControlEventValueChanged];
}
// self.currentViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
// [self addChildViewController:self.currentViewController];
// [self addSubview:self.currentViewController.view toView:self.containerView];
// //[self changeSegment];
[self.segmenter addTarget:self action:#selector(changedSegmentedControl:) forControlEvents:UIControlEventValueChanged];
// Do any additional setup after loading the view.
}
- (void)changedSegmentedControl:(UISegmentedControl*)sender
{
NSLog(#"Changed value of segmented control to %ld", sender.selectedSegmentIndex);
NSLog(#"self.segmenter value of segmented control to %ld", self.segmenter.selectedSegmentIndex);
// Code to change View Controller goes here
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
}
-(void)loadViewFromData{
// [self viewDidLoad];
// [self viewWillAppear:YES];
// [self reloadInputViews];
self.segmenter.selectedSegmentIndex = 1;
[self changedSegmentedControl:self.segmenter];
[self.segmenter sendActionsForControlEvents:UIControlEventValueChanged];
}
-(void)changeSegment{
AppDelegate *appD = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if ([[appD.defaults objectForKey:#"segment"] isEqualToString:#"index0"]) {
self.currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr1"];
}else{
self.currentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr2"];
self.segmenter.selectedSegmentIndex = 1;
}
self.currentViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self addChildViewController:self.currentViewController];
[self addSubview:self.currentViewController.view toView:self.containerView];
}
- (IBAction)showComponent:(UISegmentedControl *)sender {
if (sender.selectedSegmentIndex == 0) {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr1"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
} else {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"viewControllr2"];
newViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
[self cycleFromViewController:self.currentViewController toViewController:newViewController];
self.currentViewController = newViewController;
}
}
- (void)cycleFromViewController:(UIViewController*) oldViewController
toViewController:(UIViewController*) newViewController {
[oldViewController willMoveToParentViewController:nil];
[self addChildViewController:newViewController];
[self addSubview:newViewController.view toView:self.containerView];
newViewController.view.alpha = 0;
[newViewController.view layoutIfNeeded];
[UIView animateWithDuration:0.5
animations:^{
newViewController.view.alpha = 1;
oldViewController.view.alpha = 0;
}
completion:^(BOOL finished) {
[oldViewController.view removeFromSuperview];
[oldViewController removeFromParentViewController];
[newViewController didMoveToParentViewController:self];
}];
}
- (void)addSubview:(UIView *)subView toView:(UIView*)parentView {
[parentView addSubview:subView];
NSDictionary * views = #{#"subView" : subView,};
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[subView]|"
options:0
metrics:0
views:views];
[parentView addConstraints:constraints];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
I am calling loadViewFromData from another view controller and changing the appD.defults value to "index1" but its not redirecting
If I am trying to select the second segment via code i.e., calling externally through selectedSegmentIndex, it is not functioning and not getting re-directed.
Manually if I click and select a segment it is functioning properly.
I want to add a green line below UIButton for selected state, like following image.
I have set content offset for image and title in storyboard but i am facing issue while using auto layouts. Also selected state has image below text and un-selected state has no image below. What is the best way to handle this?
Here is a subclass that I have created for our App.
Header file:
#import <UIKit/UIKit.h>
IB_DESIGNABLE
#interface CMTabButton : UIButton
/// The color for the bottom line of the button when it is selected
#property (nonatomic)IBInspectable UIColor *selectedColor;
/// The color for the bottom line of the button when it is not selected
#property (nonatomic)IBInspectable UIColor *defaultColor;
/// The height of the bottom line of the button (default 2 pixels)
#property(nonatomic,assign)IBInspectable CGFloat bottomLineHeight;
#end
Implementation file:
#import "CMTabButton.h"
#interface CMTabButton() {
UIView *bottomLine;
NSLayoutConstraint *bottomLineHeightConstraint;
}
#end
#implementation CMTabButton
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (void)didMoveToWindow {
[super didMoveToWindow];
[self uppdateUI];
}
- (void)setup {
//add the bottom line
[self addBottomLine];
}
- (void)setSelected:(BOOL)selected {
//if the unselected color is transparent, we animate the line in/out by changing the height of the line
if (self.defaultColor==nil) {
bottomLineHeightConstraint.constant = (selected) ? self.bottomLineHeight : 0;
//we force the selected color for the animation so the animation is visible
bottomLine.backgroundColor = self.selectedColor;
[UIView animateWithDuration:0.15
animations:^{
[self layoutIfNeeded];
}
completion:^(BOOL finished) {
if (!selected)
bottomLine.backgroundColor = self.defaultColor;
[super setSelected:selected];
}
];
}
else {
bottomLineHeightConstraint.constant = self.bottomLineHeight;
//else, we animate the color change
[UIView animateWithDuration:0.15
animations:^{
bottomLine.backgroundColor = selected ? self.selectedColor : self.defaultColor;
}
completion:^(BOOL finished) {
[super setSelected:selected];
}
];
}
}
- (void)addBottomLine {
bottomLine = [UIView new];
bottomLine.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:bottomLine];
bottomLineHeightConstraint = [NSLayoutConstraint constraintWithItem:bottomLine attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1 constant:0];
[self addConstraint:bottomLineHeightConstraint];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bottomLine]|" options:0 metrics:nil views:#{#"bottomLine":bottomLine}]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[bottomLine]|" options:0 metrics:nil views:#{#"bottomLine":bottomLine}]];
}
- (void)prepareForInterfaceBuilder {
[self uppdateUI];
}
- (void)uppdateUI {
bottomLine.backgroundColor = self.selected ? self.selectedColor : self.defaultColor;
if (self.bottomLineHeight>0) {
bottomLineHeightConstraint.constant = self.bottomLineHeight;
[self layoutIfNeeded];
}
}
#end
To use it, select your UIButton on Interface builder, then change the class name of it from UIButton to CMTabButton and then you can set the properties from the Interface builder directly
I have the following UIView hierarchy:
-UIView
-UIScrollView
My constraint for UIScrollview with relation to it's super view are very simple:
#"H:|-%f-[%#]-%f-|"
and
#"V:|-%f-[%#]-%f-|"
They are working as expected.
I am trying to add a UIImageView as subview of scrollview Horizontal.
So my view hierarchy will become:
-UIView
-UIScrollView
-UIImageView
I am adding UIImageView as subview programmatically in UIScrollView using a for loop.
In the for loop, how can I achieve:
[SuperView]-10-[scrollview]-10-[UIImageView]-10-[UIImageView]-10-[UIScrollView]-10-[SuperView]
The problematic section is the bold part.
What I have tried:
for(int i=1;i<3;i++)
{
UIImageView *image = [[UIImageView alloc] init];
[image setImage:[UIImage imageNamed:[NSString stringWithFormat:#"%d.jpg",i]]];
image.translatesAutoresizingMaskIntoConstraints = NO;
[_scrollView addSubview:image];
UIView *superView = _scrollView;
NSDictionary * views = NSDictionaryOfVariableBindings(superView, image);
NSString *formate = [NSString stringWithFormat:#"H:|-%f-[%#]-%f-|", scrollViewLeftMarginFromParent, #"image", scrollViewRightMarginFromParent];
NSArray * WIDTH_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
formate = [NSString stringWithFormat:#"V:|-%f-[%#]-%f-|", scrollViewTopMarginFromParent, #"image", scrollViewBottomMarginFromParent];
NSArray * HEIGHT_CONSTRAINT = [NSLayoutConstraint constraintsWithVisualFormat:formate options:0 metrics:nil views:views];
[superView addConstraints:WIDTH_CONSTRAINT];
[superView addConstraints:HEIGHT_CONSTRAINT];
}
The approach I can think of:
LeftSide:
[scrollview]-10-[UIImageView]
Right side:
[UIImageView]-10-[scrollview]
in between:
[UIImageView]-10-[UIImageView]
If it's the right approach, then how do I achieve this in for loop.
If it's not then what is best approach.
It's quite simple actually. Your approach is correct, all you need is how you convert that into code. I will try to simplify this for you. I am assuming a UIImageView's width & height as 100. You can change as you like
-(void)setUI
{
lastView = nil; //Declare a UIImageView* as instance var.
arrayCount = [array count]; //In your case a static count of 3
for(NSInteger index =0; index < arrayCount; index++)
{
UIImageView *view = [[UIImageView alloc] init];
[self.mainScroll addSubview:view];
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20-[view(100)]-20-|" options:0 metrics:nil views:#{#"view":view}]];
//--> If view is first then pin the leading edge to main ScrollView otherwise to the last View.
if(lastView == nil && index == 0) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-10-[view(100)]" options:0 metrics:nil views:#{#"view":view}]];
}
else {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[lastView]-10-[view(100)]" options:0 metrics:nil views:#{#"lastView":lastView, #"view":view}]];
}
//--> If View is last then pin the trailing edge to mainScrollView trailing edge.
if(index == arrayCount-1) {
[self.mainScroll addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[view]-10-|" options:0 metrics:nil views:#{#"view":view}]];
}
//--> Assign the current View as last view to keep the reference for next View.
lastView = view;
}
}
I had encountered similar situation where my scrollview along with its content view was created from IB, but the subviews were added programatically. Writing constraints for subviews was making the View controller bloated. Also the for loop was was getting a lots of ifs and elses,hence I wrote a UIView Subclass to handle this scenario.
Change the class type for you Content View in IB, get a reference of it, add subviews through directly setting the property stackViewItems,or methods -(void)insertStackItem:, -(void)insertStackItem:atIndex:
#import "IEScrollContentView.h"
#interface IEScrollContentView()
{
NSMutableArray * _stackViewItems;
}
#property (nonatomic,strong) NSLayoutConstraint * topConstraint;
#property (nonatomic,strong) NSLayoutConstraint * bottomConstraint;
#end
#implementation IEScrollContentView
#synthesize stackViewItems = _stackViewItems;
//-----------------------------------------------------------------//
#pragma mark - Init Methods
//-----------------------------------------------------------------//
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
if(self = [super initWithCoder:aDecoder])
_stackViewItems = [NSMutableArray new];
return self;
}
-(instancetype)initWithFrame:(CGRect)frame {
if(self = [super initWithFrame:frame])
_stackViewItems = [NSMutableArray new];
return self;
}
//-----------------------------------------------------------------//
#pragma mark - Public Methods
//-----------------------------------------------------------------//
-(void)setStackViewItems:(NSArray *)stackViewItems {
if(!_stackViewItems)
_stackViewItems = [NSMutableArray new];
for (UIView * view in stackViewItems) {
[self insertStackItem:view];
}
}
-(void)insertStackItem:(UIView *)stackItem
{
[self insertStackItem:stackItem atIndex:_stackViewItems.count];
}
-(void)insertStackItem:(UIView *)stackItem atIndex:(NSUInteger)index
{
if(!stackItem || index > _stackViewItems.count)return;
if(index == 0)
[self addView:stackItem
belowView:self
aboveView:_stackViewItems.count>0?_stackViewItems.firstObject:self];
else if(index==_stackViewItems.count)
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:self];
else
[self addView:stackItem
belowView:_stackViewItems[index-1]
aboveView:_stackViewItems[index]];
}
//-----------------------------------------------------------------//
#pragma mark - Constraining Views
//-----------------------------------------------------------------//
-(void)addView:(UIView *)view belowView:(UIView *)viewAbove aboveView:(UIView *)viewBelow {
view.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:view];
NSArray * defaultConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)];
NSLayoutConstraint * upperConstraint,* lowerConstraint;
if(viewAbove==self) {
[self removeConstraint:_topConstraint];
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_topConstraint = upperConstraint;
}
else
upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[viewAbove]-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewAbove)].firstObject;
if(viewBelow==self) {
[self removeConstraint:_bottomConstraint];
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
_bottomConstraint = lowerConstraint;
}
else
lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[view]-0-[viewBelow]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewBelow)].firstObject;
[self addConstraints:defaultConstraints];
[self addConstraints:#[upperConstraint,lowerConstraint]];
[_stackViewItems addObject:view];
}
#end
I have uploaded the files here
IEScrollContentView.h
IEScrollContentView.h.m
I have a view controller presenting another view controller with modalPresentationStyle = UIModalPresentationCustom. Things are set up so that part of the presenting view controller's view shows up underneath the presented view controller's view. In this state, the presenting view controller still handles auto-rotation correctly, and I handle rotation for the presented view controller using autolayout.
I'm now trying to implement interactively dismissing the presented view controller using iOS 7's custom view controller transitioning API. It works except that, when the interactive dismissal is canceled, handling of auto-rotation stops working. (It works again after the presented view controller is dismissed later.) Why is this happening, and how can I fix this?
EDIT: Here is code you can run to demonstrate the problem. A view pops up from below, and you can dismiss it by swiping it down. If you cancel dismissal by not swiping it all the way down, the presenting view controller's view no longer responds to rotations, and the presented view controller's view has messed-up layout.
EDIT: Here is the link to the code below as an Xcode project:
https://drive.google.com/file/d/0BwcBqUuDfCG2YlhVWE1QekhUWlk/edit?usp=sharing
Sorry for the massive code dump, but I don't know what I'm doing wrong. Here's a sketch of what is going on: ViewController1 presents ViewController2. ViewController1 implements UIViewControllerTransitioningDelegate, so it is returning the animation/interactive controllers for the transitions. ViewController2 has a pan gesture recognizer that drives the interactive dismissal; it implements UIViewControllerInteractiveTransitioning to serve as the interactive controller for dismissal. It also keeps a reference to the animation controller for dismissal to finish the transition if the user drags the view down far enough. Finally, there are two animation controller objects. PresentAnimationController sets up the autolayout constraints to handle rotations for the presented view controller's view, and DismissAnimationController finishes up the dismissal.
ViewController1.h
#import <UIKit/UIKit.h>
#interface ViewController1 : UIViewController <UIViewControllerTransitioningDelegate>
#end
ViewController1.m
#import "ViewController1.h"
#import "ViewController2.h"
#import "PresentAnimationController.h"
#import "DismissAnimationController.h"
#implementation ViewController1
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = #"View 1";
self.navigationItem.prompt = #"Press “Present” and then swipe down to dismiss.";
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Present" style:UIBarButtonItemStylePlain target:self action:#selector(pressedPresentButton:)];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
// Some subview just to check if layout is working.
UIView * someSubview = [[UIView alloc] initWithFrame:self.view.bounds];
someSubview.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
someSubview.backgroundColor = [UIColor orangeColor];
someSubview.layer.borderColor = [UIColor redColor].CGColor;
someSubview.layer.borderWidth = 2;
[self.view addSubview:someSubview];
}
// --------------------
- (void)pressedPresentButton:(id)sender
{
ViewController2 * presentedVC = [[ViewController2 alloc] initWithNibName:nil bundle:nil];
presentedVC.modalPresentationStyle = UIModalPresentationCustom;
presentedVC.transitioningDelegate = self;
[self presentViewController:presentedVC animated:YES completion:nil];
}
// --------------------
// View Controller Transitioning Delegate Methods.
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
return [[PresentAnimationController alloc] init];;
}
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
DismissAnimationController * animationController = [[DismissAnimationController alloc] init];
ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController;
if (presentedVC.dismissalIsInteractive) {
presentedVC.dismissAnimationController = animationController;
}
return animationController;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator
{
return nil;
}
- (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator
{
ViewController2 * presentedVC = (ViewController2 *)self.presentedViewController;
if (presentedVC.dismissalIsInteractive) {
return presentedVC;
}
else {
return nil;
}
}
#end
ViewController2.h
#import <UIKit/UIKit.h>
#import "DismissAnimationController.h"
#interface ViewController2 : UIViewController <UIViewControllerInteractiveTransitioning>
#property (weak, nonatomic) UIView * contentView;
#property (nonatomic, readonly) BOOL dismissalIsInteractive;
#property (strong, nonatomic) DismissAnimationController * dismissAnimationController;
#end
ViewController2.m
#import "ViewController2.h"
#interface ViewController2 ()
#property (strong, nonatomic) id<UIViewControllerContextTransitioning> transitionContext;
#end
#implementation ViewController2
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
_dismissalIsInteractive = NO;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
// Set up content view.
CGRect frame = UIEdgeInsetsInsetRect(self.view.bounds, UIEdgeInsetsMake(15, 15, 15, 15));
UIView * contentView = [[UIView alloc] initWithFrame:frame];
self.contentView = contentView;
contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
contentView.backgroundColor = [UIColor cyanColor];
contentView.layer.borderColor = [UIColor blueColor].CGColor;
contentView.layer.borderWidth = 2;
[self.view addSubview:contentView];
// Set up pan dismissal gesture recognizer.
UIPanGestureRecognizer * panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(dismissalPan:)];
[self.view addGestureRecognizer:panGesture];
}
// --------------------
- (void)dismissalPan:(UIPanGestureRecognizer *)panGesture
{
switch (panGesture.state) {
case UIGestureRecognizerStateBegan: {
_dismissalIsInteractive = YES;
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
break;
}
case UIGestureRecognizerStateChanged: {
CGPoint translation = [panGesture translationInView:self.view];
CGFloat percent;
if (translation.y > 0) {
percent = translation.y / self.view.bounds.size.height;
percent = MIN(percent, 1.0);
}
else {
percent = 0;
}
// Swiping content view down.
CGPoint center;
center.x = CGRectGetMidX(self.view.bounds);
center.y = CGRectGetMidY(self.view.bounds);
if (translation.y > 0) {
center.y += translation.y; // Only allow swiping down.
}
self.contentView.center = center;
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:(0.5 * (1.0 - percent))];
[self.transitionContext updateInteractiveTransition:percent];
break;
}
case UIGestureRecognizerStateEnded: // Fall through.
case UIGestureRecognizerStateCancelled: {
_dismissalIsInteractive = NO;
id<UIViewControllerContextTransitioning> transitionContext = self.transitionContext;
self.transitionContext = nil;
DismissAnimationController * dismissAnimationController = self.dismissAnimationController;
self.dismissAnimationController = nil;
CGPoint translation = [panGesture translationInView:self.view];
if (translation.y > 100) {
// Complete dismissal.
[dismissAnimationController animateTransition:transitionContext];
}
else {
// Cancel dismissal.
void (^animations)() = ^() {
CGPoint center;
center.x = CGRectGetMidX(self.view.bounds);
center.y = CGRectGetMidY(self.view.bounds);
self.contentView.center = center;
self.view.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5];
};
void (^completion)(BOOL) = ^(BOOL finished) {
[transitionContext cancelInteractiveTransition];
[transitionContext completeTransition:NO];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion];
}
break;
}
default: {
break;
}
}
}
// --------------------
// View Controller Interactive Transitioning Methods.
- (void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
self.transitionContext = transitionContext;
}
#end
PresentAnimationController.h
#import <Foundation/Foundation.h>
#interface PresentAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
#end
PresentAnimationController.m
#import "PresentAnimationController.h"
#import "ViewController2.h"
#implementation PresentAnimationController
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController * fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
ViewController2 * toVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * containerView = [transitionContext containerView];
CGPoint toCenter = fromVC.view.center;
CGRect toBounds = fromVC.view.bounds;
toVC.view.center = toCenter;
toVC.view.bounds = toBounds;
[toVC.view layoutIfNeeded];
[containerView addSubview:fromVC.view];
[containerView addSubview:toVC.view];
CGRect contentViewEndFrame = toVC.contentView.frame;
CGRect contentViewStartFrame = contentViewEndFrame;
contentViewStartFrame.origin.y += contentViewStartFrame.size.height;
toVC.contentView.frame = contentViewStartFrame;
UIColor * endBackgroundColor = toVC.view.backgroundColor;
toVC.view.backgroundColor = [UIColor clearColor];
void (^animations)() = ^() {
toVC.contentView.frame = contentViewEndFrame;
toVC.view.backgroundColor = endBackgroundColor;
};
void (^completion)(BOOL) = ^(BOOL finished) {
toVC.view.autoresizingMask = UIViewAutoresizingNone;
toVC.view.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint * centerXConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0];
NSLayoutConstraint * centerYConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeCenterY
multiplier:1
constant:0];
NSLayoutConstraint * widthConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0];
NSLayoutConstraint * heightConstraint = [NSLayoutConstraint constraintWithItem:toVC.view
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:fromVC.view
attribute:NSLayoutAttributeHeight
multiplier:1
constant:0];
[containerView addConstraint:centerXConstraint];
[containerView addConstraint:centerYConstraint];
[containerView addConstraint:widthConstraint];
[containerView addConstraint:heightConstraint];
[transitionContext completeTransition:YES];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseOut animations:animations completion:completion];
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
#end
DismissAnimationController.h
#import <Foundation/Foundation.h>
#interface DismissAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
#end
DismissAnimationController.m
#import "DismissAnimationController.h"
#import "ViewController2.h"
#implementation DismissAnimationController
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
ViewController2 * fromVC = (ViewController2 *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController * toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView * containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
[containerView addSubview:fromVC.view];
void (^animations)() = ^() {
CGRect contentViewEndFrame = fromVC.contentView.frame;
contentViewEndFrame.origin.y = CGRectGetMaxY(fromVC.view.bounds) + 15;
fromVC.contentView.frame = contentViewEndFrame;
fromVC.view.backgroundColor = [UIColor clearColor];
};
void (^completion)(BOOL) = ^(BOOL finished) {
if ([transitionContext isInteractive]) {
[transitionContext finishInteractiveTransition];
}
[transitionContext completeTransition:YES];
};
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveLinear animations:animations completion:completion];
}
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5;
}
#end
AppDelegate.m
#import "AppDelegate.h"
#import "ViewController1.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
ViewController1 * vc = [[ViewController1 alloc] initWithNibName:nil bundle:nil];
UINavigationController * nav = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nav;
[self.window makeKeyAndVisible];
return YES;
}
#end
I think I found your problem. in your PresentAnimationController.m you specify toVC.view.translatesAutoresizingMaskIntoConstraints = NO; and you set all of your constraints in the completion block you set in
- (void)animateTransition:
Comment that line out and all of the constraints and addConstraint: calls and it should work
EDIT:
Just saw it worked only when the gesture was cancelled and not when the view is initially displayed. Comment out everything in the completion block except for
[transitionContext completeTransition:YES];
I created a simple UIView with a UILabel and a UITextField inside of this UIView, this appear fine in the screen but when I touch over the UITextField, the keyboard doesn't appear.
I'm using autolayout programmatic and I'm think the problem is something about UIVIew frame.
I made a simple code to simulate this problem, the most relevant part of code is:
FormInput interface.
//FormInput.h
#import <UIKit/UIKit.h>
#interface FormInput : UIView
#property (nonatomic, strong) NSString *title;
#end
FormInput implementation
// FormInput.m
#import "FormInput.h"
#interface FormInput () <UITextFieldDelegate>
#property (nonatomic, strong) UILabel *label;
#property (nonatomic, strong) UITextField *field;
- (void)setuSubviews;
#end
#implementation FormInput
- (id)init
{
self = [super init];
if(!self) return nil;
[self setuSubviews];
return self;
}
- (void)setuSubviews
{
NSDictionary *views = #{
#"label":self.label,
#"field":self.field
};
[self addSubview:self.label];
[self addSubview:self.field];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[label(<=120)]-2-[field]-0-|" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[label]" options:0 metrics:nil views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[field]" options:0 metrics:nil views:views]];
}
#pragma mark - Lazy properties
- (UILabel *)label
{
if(!_label)
{
_label = [UILabel new];
_label.textColor = [UIColor blackColor];
_label.translatesAutoresizingMaskIntoConstraints = NO;
}
return _label;
}
- (UITextField *)field
{
if(!_field)
{
_field = [UITextField new];
_field.translatesAutoresizingMaskIntoConstraints = NO;
_field.font = [UIFont systemFontOfSize:16];
_field.placeholder = #"enter text here";
_field.keyboardType = UIKeyboardTypeDefault;
_field.keyboardAppearance = UIKeyboardAppearanceDark;
_field.returnKeyType = UIReturnKeyDone;
_field.clearButtonMode = UITextFieldViewModeWhileEditing;
_field.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
}
return _field;
}
#pragma mark - Properties setters
- (void)setTitle:(NSString *)title
{
_title = title;
self.label.text = title;
}
#end
View controller
#import "ViewController.h"
#import "FormInput.h"
#interface ViewController ()
#property (nonatomic, strong) FormInput *username;
#property (nonatomic, strong) FormInput *email;
- (void)setupLayout;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor whiteColor];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// [self setupLayout];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setupLayout];
}
#pragma mark - Lazy properties
- (FormInput *)username
{
if(!_username)
{
_username = [FormInput new];
_username.translatesAutoresizingMaskIntoConstraints = NO;
_username.title = #"Username:";
}
return _username;
}
- (FormInput *)email
{
if(!_email)
{
_email = [FormInput new];
_email.translatesAutoresizingMaskIntoConstraints = NO;
_email.title = #"Email:";
}
return _email;
}
#pragma mark - Layout setup
- (void)setupLayout
{
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
btn.translatesAutoresizingMaskIntoConstraints = NO;
[btn setTitle:#"Button" forState:UIControlStateNormal];
UITextField *field = [UITextField new];
field.translatesAutoresizingMaskIntoConstraints = NO;
field.placeholder = #"Placeholder";
[self.view addSubview:self.username];
[self.view addSubview:self.email];
[self.view addSubview:btn];
[self.view addSubview:field];
NSDictionary *views = #{
#"username" : self.username,
#"email" : self.email,
#"btn" : btn,
#"field" : field
};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[username]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[email]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[btn]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[field]-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username]-30-[email]-30-[btn]-30-[field]" options:0 metrics:nil views:views]];
}
#end
Make sure parent containers of the text field in question have "User Interaction Enabled" checked. Maybe you checked it for the text field but not your viewcont?
My reputation is "1" and I can't poste an image, but this check button should be in the interaction part...
Ok I found the reason.
The height of each view in the constraint below still intrinsic:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username]-30-[email]-30-[btn]-30-[field]" options:0 metrics:nil views:views]]
Thus, just add a vertical height for each view in the constraint visual format below.
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-30-[username(20)]-30-[email(20)]-30-[btn]-30-[field]" options:0 metrics:nil views:views]]
Not trivial but this makes the views inside added view to responds touch actions.
Thanks!