keeping constraints when replacing a view by an other view - objective-c

In my application, I have :
The main Window, containing a "custom view", which is an NSView
dropped in IB.
A View Controller + an other nib, containing a view and some
controls.
When the app loads, I am using :
initWithNibName:nibName andReplaceView:(the custom view) resize:YES
To replace the custom view. I know there is an option placeholder for the views in IB, but I do not know how to use it, and my app works well this way...
... except that the loaded view does not inherit the layout constraints of the replaced view.
How can I fix this ?
Edit: Sorry, I forgot that the function was mine... I wrote it a long time ago in a category. Here is the code :
- (id)initWithNibName:(NSString*)nibName andReplaceView:(NSView*)aView resize:(BOOL)resize
{
// 1. Loading the bundle
if (self = [self initWithNibName:nibName bundle:nil])
{
[self replaceView:aView resize:resize];
}
return self;
}
- (void)replaceView:(NSView*)aView resize:(BOOL)resize
{
if (resize)
{
NSRect insertionFrame = [aView frame];
[[self view] setFrame:insertionFrame];
}
else
{
NSRect insertionFrame = [aView frame];
insertionFrame.size.width = [[self view] frame].size.width;
insertionFrame.size.height = [[self view] frame].size.height;
[[self view] setFrame:insertionFrame];
}
NSView* supView = [aView superview];
[supView replaceSubview:aView with:[self view]];
}

When you replace a view, it removes all the layout constraints attached to the old view.
Personally, I just put the new view inside the old view. Here is some code I've used:
#implementation SJPlaceholderView
-(void) fillWithView:(NSView*)view {
NSParameterAssert(view);
view.frame = self.bounds;
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
[self addSubview:view];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)]];
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(view)]];
}
#end
This makes sure the inner view frame matches the outer view frame exactly. All the layout constraints on the outer view are still in effect.
You could also try to loop through all the constraints of the old view, and apply them to the new view. Most of the constraints will be on the view itself, or the superview, but they could theoretically be on any of the ancestor views.

Related

iOS 8 programmatic contraints for PageViewController

I have a PageViewController which I am programmatically adding to the current view via the following:
// Create the page view controller.
self.pageViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"homePageViewController"];
self.pageViewController.dataSource = self;
self.pageViewController.view.frame = CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height);
// Instantiate the first view controller.
UIViewController *startingViewController = [self viewControllerAtIndex:0];
[self.pageViewController setViewControllers:#[startingViewController]
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:^(BOOL finished) {
// Completion code
}];
// Add the page view controller to this root view controller.
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
[self.pageViewController didMoveToParentViewController:self];
This is working well, however I need to add a constraint the this as I have an image view above and I want the top of the PageViewController to attach to the bottom of the ImageView. I have tried:
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.headerImage attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.pageViewController attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
and although the code builds fine, I get and error
Constraint items must each be an instance of UIView or subclass'
I've also tried:
NSDictionary *views = #{#"view": self.pageViewController.view,
#"top": self.headerImage };
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"[top][view]" options:0 metrics:nil views:views]];
And although this build and runs without error, the constraint is not observed.
Any help greatly appreciated. Thanks

gap between keyboard and textview during animation

I want to adjust the height of a textview when the keyboard appears. In iOS 7 this can be done by adjusting the NSLayoutConstraint between the textview and the bottomLayoutGuide of the view controller.
That works fine with the code below except for one detail. During the animation the textview runs ahead of the keyboard and a wide gap appears. Similar during the keyboardWillHide method.
The cause for this "bug" is probably because the keyboard starts from the very bottom of the screen while the textview starts higher up due to the height of the toolbar.
Any suggestions on how to fix this?
-(void) keyboardWillShow:(NSNotification *) notification
{
//update constraints
CGRect keyboardFrame = [[[notification userInfo] valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedKeyboardFrame = [[self view] convertRect:keyboardFrame
fromView:nil];
CGRect toolbarFrame = [[[self navigationController] toolbar] frame];
CGRect convertedToolbarFrame = [[self view] convertRect:toolbarFrame
fromView:nil];
CGFloat toolbarAdjustment = (UIInterfaceOrientationIsPortrait([self interfaceOrientation])) ? CGRectGetHeight(convertedToolbarFrame) :CGRectGetWidth(convertedToolbarFrame);
[[_textView bottomSpaceConstraint] setConstant:CGRectGetHeight(convertedKeyboardFrame) - toolbarAdjustment];
//animate change
for (UIView *view in [[self view] subviews])
{
[view setNeedsUpdateConstraints];
}
[UIView animateWithDuration:[[[notification userInfo] valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue]
delay:0 //0.2 as possible hack, otherwise a gap appears between keyboard and textview
options:[[[notification userInfo] valueForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue]
animations:^{
for (UIView *view in [[self view] subviews])
{
[view layoutIfNeeded];
}
}
completion:NULL];
}
The delay is probably caused by calling layoutIfNeeded repeatedly in a loop.
In the animation block, just send layoutIfNeeded to the root view, i.e., self.view. Sending layoutIfNeeded to the root view will take care of the entire view hierarchy. So get rid of the loop.
I question if the call to setNeedsUpdateConstraints is necessary; if it is, it only needs to be sent to the root view.
Also, try eliminating the animation options parameter
options:0

Flip view controllers inside bigger view controller will flip the main view controller

I want to flip a view controller which has smaller size than the screen. I am using transitionFromViewController but this would flip also the whole screen. And I want just the smaller view controller to be flipped.
First the login should be visible, after flipped the register view should be available!
My code is
- (void)showLogin {
vcLogin = [[LoginViewController alloc] initWithNibName:#"LoginViewController" bundle:nil];
[self.view addSubview:vcLogin.view];
[self addChildViewController:vcLogin];
if (IS_IPAD()) {
vcLogin.view.center = self.view.center;
}
}
- (void)showRegister {
vcRegister = [[RegisterViewController alloc] initWithNibName:#"RegisterViewController" bundle:nil];
[self.view addSubview:vcRegister.view];
[self addChildViewController:vcRegister];
if (IS_IPAD()) {
vcRegister.view.center = self.view.center;
}
[vcLogin willMoveToParentViewController:nil];
[self transitionFromViewController:vcLogin toViewController:vcRegister duration:0.5f options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{} completion:^(BOOL finished) {
[vcLogin removeFromParentViewController];
[vcRegister didMoveToParentViewController:self];
}];
}
I also tried with [UIView transitionFromView:...] but the result is the same!
Any suggestions? Thanks!
Have you tried core animation? You might use something like the code below and have one view controller with subviews.
[UIView transitionFromView:viewOne
toView:viewTwo
duration:1.0f
options:UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionShowHideTransitionViews
completion:(^(BOOL finished){
})];

How to fade out a subview to show a UIViewController's view loaded from a .nib

I have ViewController which is loaded from a .nib file. in the viewDidLoad method I create a subview and add it to the view hierarchy. How do I fade out that subview to show the view in .nib file?
(the subview is like a splash screen, which I want to fade out to show the view in the .nib, it's set up this way since it was easiest way for me.)
Here is some of my code (I tried to set a reference to the original view from the nib in the viewDidLoad but couldn't get it to work):
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"View did load");
//set reference to view in .nib here
UIView *currentView = self.view;
CGRect frame = [[UIScreen mainScreen] bounds];
splashView = [[splashView alloc] initWithFrame:frame];
[[self view] addSubview:splashView];
//transition did not work
[UIView transitionFromView:splashView toView:currentView duration:0.25 options:UIViewAnimationOptionTransitionCrossDissolve completion:^(BOOL finished) {
NSLog(#"transition finished");
}];
}
That code crashes. What am I doing wrong?
Try the following in place of your original code:
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"View did load");
CGRect frame = [[UIScreen mainScreen] bounds];
splashView = [[splashView alloc] initWithFrame:frame];
[[self view] addSubview:splashView];
[self.view bringSubviewToFront: splashView];
[UIView animateWithDuration: 2.0
delyay: 0.5 // omit if you don't need a delay
options: UIViewAnimationCurveEaseOut // check the documentation for other options
animations: ^{
splashView.alpha = 0;
}
completion: ^(BOOL finished) {
[splashView removeFromSuperView];
}];
I don't know if you're using ARC or not, or if you using storyboards or not!
If you're note using ARC, then memory management is wrong in this snippet.

Does it make sense to add ATMHud to tabBarController.view

When i try to add ATMHud to uitableviewcontroller subview it does work but it doesn't disable the scrolling and if the tableview is not on top i can't see the hud view. What i did was added to teh tabBarController.view that works but i want find out if this is a good idea or later on i might have issues with it.
Another question is tabBarController.view frame is that the whole screen or just the bottom part. How come atmhud shows in the middle of the screen?
Thanks in advance!
Yan
============
Found a blog post that shows how to reset self.view and add tableview separately in uitableviewcontroller
UITableViewController and fixed sub views
- (void)viewDidLoad {
[super viewDidLoad];
if (!tableView &&
[self.view isKindOfClass:[UITableView class]]) {
tableView = (UITableView *)self.view;
}
self.view = [[[UIView alloc] initWithFrame:
[UIScreen mainScreen].applicationFrame] autorelease];
self.tableView.frame = self.view.bounds;
self.tableView.contentInset = UIEdgeInsetsMake(44.0, 0.0, 0.0, 0.0);
[self.view addSubview:self.tableView];
UIView *fixedBar = [[UIView alloc] initWithFrame:
CGRectMake(0.0, 0.0, self.view.bounds.size.width, 44.0)];
fixedBar.backgroundColor = [UIColor colorWithRed:
0.0 green:1.0 blue:0.0 alpha:0.7];
[self.view addSubview:fixedBar];
[fixedBar release];
}
After this when add hud to self.view you will be able to disable the tableview on the bottom.
Let me know if this a good way to setup the tableview
The problem with using the tab bar is that the hud is now modal, and the user cannot change the tab.
It sounds like the tableview is not your primary viewm, as it can get "covered up". If its not the primary view, then add the ATMHud to self.view. If the tableView is the same as self.view, then add a new transparent view to it, then add the HUD to that view.
The tabBarController.view is the view that hosts the tabbed views - if you want to see its size (or frame) log it using NSStringFromCGRect(self.tabBarController.frame);
EDIT: I just did a test, the ATMHud DOES block the UI. All I can think of is that you have not inserted it where you need to (at the top of current view's subviews.) I have a demo project where I do this:
hud = [[ATMHud alloc] initWithDelegate:self];
[self.view addSubview:hud.view];
[hud setCaption:#"Howdie"];
[hud setActivity:YES];
[hud show];
[hud hideAfter:5];
A button under the hud is not active - in fact nothing in the view is active (probably the Nav Bar would be live though)
If you want an ARCified and field tested version, you can grab it here
EDIT2: The solution to your problem is below. Note that ATMHud blocks clicks from getting to the table, and the code below stops the scrolling:
- (void)hudWillAppear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = NO;
}
- (void)hudDidDisappear:(ATMHud *)_hud
{
self.tableView.scrollEnabled = YES;
}
Dump the views:
#import <QuartzCore/QuartzCore.h>
#import "UIView+Utilities.h"
#interface UIView (Utilities_Private)
+ (void)appendView:(UIView *)v toStr:(NSMutableString *)str;
#end
#implementation UIView (Utilities_Private)
+ (void)appendView:(UIView *)a toStr:(NSMutableString *)str
{
[str appendFormat:#" %#: frame=%# bounds=%# layerFrame=%# tag=%d userInteraction=%d alpha=%f hidden=%d\n",
NSStringFromClass([a class]),
NSStringFromCGRect(a.frame),
NSStringFromCGRect(a.bounds),
NSStringFromCGRect(a.layer.frame),
a.tag,
a.userInteractionEnabled,
a.alpha,
a.isHidden
];
}
#end
#implementation UIView (Utilities)
+ (void)dumpSuperviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
while(v) {
[self appendView:v toStr:str];
v = v.superview;
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
+ (void)dumpSubviews:(UIView *)v msg:(NSString *)msg
{
NSMutableString *str = [NSMutableString stringWithCapacity:256];
if(v) [self appendView:v toStr:str];
for(UIView *a in v.subviews) {
[self appendView:a toStr:str];
}
[str appendString:#"\n"];
NSLog(#"%#:\n%#", msg, str);
}
#end