Shifting view after displaying modal - possibly AutoLayout related - objective-c

I present a simple view with a couple of labels and a button, all inside a UIScrollView and laid out using auto layout.
The button presents another view, which includes a navigation item for dismissal.
After dismissal, though, the content of the original UIScrollView is offset. Strangely, the amount by which it is offset seems related to the scroll position at the time of presentation.
The demo project here is a small example of this issue. Run it in the iPhone simulator and scroll to the bottom to use the 'modal' button. After dismissing the modal attempt to scroll back to the top - the issue should be clear.
Or refer to the scroll bar in the images below to see the problem.
BEFORE PRESENTATION
AFTER PRESENTATION

I'm not an expert in AutoLayout, but I fixed it by adding the label & button constraints to self.view instead of self.scrollView.
For example:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1]"
options:0
metrics:nil
views:#{#"l1":self.l1}]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[l1]"
options:0
metrics:nil
views:#{#"l1":self.l1}]];
Why this fixes it... have no idea :D

I've had this same problem, and after much investigation it appears to be a bug in UIKit relating to scrollviews and AutoLayout. Here's the 'fix'...
In viewDidDisappear:, save the current scrollview contentOffset to a property, and reset it to zero:
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
self.previousContentOffset = self.scrollView.contentOffset;
self.scrollView.contentOffset = CGPointZero;
}
Then, in viewWillAppear:, reset the content offset back to what it was previously. I had to dispatch this onto the main queue to get it to work correctly:
- (void)viewWillAppear:(BOOL)animated
{
if (!CGPointEqualToPoint(self.previousContentOffset, CGPointZero))
{
dispatch_async(dispatch_get_main_queue(), ^{
self.scrollView.contentOffset = self.previousContentOffset;
});
}
}

Related

UITableViewCells scroll horizontally using auto layout when changing to portrait mode

I am creating a screen that is primarily a UITableView with cells that have differing controls and layouts. I had the tableview working great until I switched to portrait mode where it allowed the tableview cells to scroll horizontally. The cells should never scroll horizontally; only vertically. When I navigate to the screen and I'm already in portrait mode it works fine, even going to landscape and back to portrait.
Some things to note are that I'm using and iPad, storyboards, auto layout, and the UITableViewCell is custom and does not have a nib (though it does the same thing when I tried using a nib). All controls and layout constraints are added programmatically. I am using a custom view controller with a view inside of it. Inside of the view I have the tableview and a toolbar. If I remove the view so that the tableview is directly under the custom view controller it works fine, but then I can't have the toolbar.
Here is the test code I am using to troubleshoot the problem:
// Remove all existing subviews
for(UIView *subView in self.contentView.subviews)
{
[subView removeFromSuperview];
}
UILabel *testLabel = [[UILabel alloc] init];
testLabel.text = #"Test";
[testLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
self.contentView addSubview:testLabel];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(testLabel);
NSString *format = [NSString stringWithFormat:#"V:|-[testLabel]-|"];
NSArray *constraintsArray = [NSLayoutConstraint constraintsWithVisualFormat:format options:nil metrics:nil views:viewsDictionary];
//[self.contentView addConstraints:constraintsArray];
The scrolling works as intended until I uncomment the line to add the constraints. This code is in a method of my custom cell and gets called every time a cell is dequeued. I've tried adding the contraints individually, without using the visual format, with no luck. I have also done some research and found the code that removes the contraints from the cell and adds them to the contentView, but that hasn't helped either.
What do I need to do to keep the cells from scrolling horizontally when changing from landscape to portrait mode?
MORE INFO:
After some more debugging I found that the table view's content size is not shrinking horizontally. The width is staying at 1024.

Resizing a custom view with the window

So I've seen this question asked a couple times, but haven't come across an answer that solves my problem. Right now I basically have MainMenu.xib with a label centered on the top, a large custom view and a button centered on the bottom that switches subviews of the custom view (see picture below). I've set the window and the custom view to autoresize subviews in the interface builder and all the buttons, labels, etc. have constraints relating them to the sides of the view, but when I resize the window, the contents of the subview do not resize. I think the custom view is resizing with the window because when I switch subviews with the button, the subview that loads is resized with the window, they just aren't resizing with the window in real time.
MainMenu.xib:
Normal subview:
After window is expanded:
Subview switched and then switched back:
CONSTRAINTS:
MainMenu.xib:
BlockViewController.xib:
Figured it out! Really simple, just one line of code in the awakeFromNib method of each ViewController subclass:
[self.view setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
From the pictures and the description in the comments it seems that the problem is that the custom view does not get any layout constraints to determine how to resize the subview.
The following piece of code (typed directly in the browser, so beware) should provide the necessary constraints to "glue" the borders of the subview to the custom view.
[self.customView addSubview: self.blockSubView.view];
self.blockSubView.view.frame = self.customView.bounds;
NSView *blockSubView = self.blockSubView.view;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(blockSubView);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[blockSubView]|"
options:0
metrics:nil
views:viewsDictionary];
constraints = [constraints arrayByAddingObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat:#"V|[blockSubView]|"
options:0
metrics:nil
views:viewsDictionary]];
[self.customView addConstraints: constraints];
There is more about this in the docs. Notice the tricks for debugging - very useful to the point of being indispensable.

UIScrollView not scrolling when adding item in IB and code

When I create a Scrollview on my scene, and then add a button to scene in IB. Then I go into the code, set the content size, enable user interaction and add another button. When I run the program in the simulator the Scrollview does not work, if I remove the button that is in IB on the scene it works just fine. Is it not possible to add items to the scrollview both in IB and programmatically?
EDIT: I thought it may be something in the app I already had. So I decided that I would create anew project and all it has in it is code, and the scene picture below. It is indeed added below the ScrollView.
UIButton *myButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[myButton setTitle:#"My Button" forState:UIControlStateNormal];
myButton.frame = CGRectMake(100, 100, 150, 50);
[scrollView addSubview:myButton];
scrollView.userInteractionEnabled = YES;
[scrollView setContentSize:CGSizeMake(320, 1000)];
Here your scrollView is not scrolling due to the autoLayout, uncheck the auto Layout if you are not using.
I Just made a similar to your requirement. It is working fine, and after allowing autoLayout it just stopped scrolling.
The auto layout constraints fits to the visible part of the screen, if the objects in scrollView are more then screen size, it will scroll.
So my suggestion if you are not using autoLayout just uncheck it, and works fine.
Here is an helpful link: http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html#//apple_ref/doc/uid/TP40012166-CH1-SW19
Basically what this says is that, with auto-layout you don't and shouldn't have to use setContentSize:. Instead your inner view should have it's edges snapped to the edges of the scrollview. Let me demonstrate how I solved it.
scrollBackground : a view that contains every other view that needs to scroll in the scrollview.
[scrollview addSubview:scrollBackground];
[scrollview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[scrollBackground(1000.0f)]|"
options:0 metrics:nil
views:NSDictionaryOfVariableBindings(scrollBackground)]];
[scrollview addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"H:|[scrollBackground(==scrollview)]|"
options:0 metrics:nil
views:NSDictionaryOfVariableBindings(scrollBackground)]];
The key here being those | at beginning and end of the VisualFormat String. These will tell the scrollview what is the contentSize. Of course if you have a contentSize smaller than the frame size of the scrollview it won't scroll in that direction. In my example this is true for width. It only scrolls up and down.
Make sure you set your content size in viewDidLoad or at some point after the view has already been loaded from the nib file.
As vishy Pointed out, your Button should be part of the ScrollView's Hierarchy, else you'll just be scrolling an empty view.
In the case you posted, the scrollview will not scroll because all of the content is visible. Try changing that last line to:
[scrollView setContentSize:CGSizeMake(50, 50)];
and it will start scrolling, because the content size is smaller than all of the content in the view.
check d2burke answer in this question
you should place your code in viewWillLayoutSubviews instead of viewDidAppear

How to make detail view scrollable in master detail app

I have only been working in iOS for a few months but I have been banging my head against the wall for hours and hours for something that seems like it should be pretty straightforward. I used the master detail template in Xcode for an iPad app. I want the detail portion to be scrollable to show content below what is visible in the frame, in either orientation. I have tried numerous combinations of adding scrollviews in the DetailViewController in viewWillAppear, viewDidLoad, loadView...and the best I can come up with is what looks like a scrollable view on the top layer as it does show scroll bars and shows me that I did the scrollView.contentSize correctly as I can pan around, but the actual view with the fields and stuff doesn't move and the fields are unable to be edited. Here is my viewDidAppear as it stands at the moment. As you can see in the NSLogs I am trying to understand the view stack. If I uncomment the line before the logs, I lose the scroll bars altogether.
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 768, 1024)];
scrollView.contentSize = CGSizeMake(2048, 2048);
UIView *parentView = [[UIView alloc] init];
parentView = [[self view] superview];
[[parentView superview] addSubview:scrollView];
//[scrollView addSubview:[self view]];
NSLog(#"%#", [parentView superview]);
NSLog(#"%#", parentView);
NSLog(#"%#", [super view]);
NSLog(#"%#", [self view]);
[scrollView setDelegate:self];
}
I would sincerely appreciate any guidance or tips on how to properly implement UIScrollView for this scenario.
You should add the UIScrollView in IB. Be sure to move all of your existing views and controls to be subviews of the scroll view. And you'll have to link the scroll view to an IBOutlet so you can set the content size in your view controller.
Instead of trying to wrap a uiscrollview around your main view's superview (which you have incorrectly tried to take ownership of (it should be NULL anyways)), why not herd your UI elements into a full-sized subview and wrap the scrollview around that? It would be much much easier.
Your hierarchy would look like this:
Self.view -> ScrollView (with contentSize set) -> UIView containing elements that need to be scrolled -> various UI elements

UITableViewCell subclass, drawn in code, animate Delete button in

I'm working on a custom UITableViewCell subclass, where everything is drawn in code rather than using UILabels etc. (This is part learning exercise and partly because drawing in code is much faster. I know that for a couple of labels it wouldn't make a huge difference, but eventually I'll want to generalise this to more complex cells.)
Currently I'm struggling with the delete button animation: how to animate the cell shrinking as the delete button slides in.
Firstly, I am drawing in a custom subview of the cell's contentView. Everything is drawn in that one subview.
I am setting the subview's size by catching layoutSubviews on the cell itself, and doing:
- (void)layoutSubviews
{
[super layoutSubviews];
CGRect b = [self.contentView bounds];
[subcontentView setFrame:b];
}
I'm doing this rather than just setting an autoresizing mask because it seemed more reliable in testing, but I can use the autoresizing mask approach in testing if needed.
Now, the default thing that happens when someone hits the minus is the view gets squished.
I can avoid that by, when setting up my cell, calling
subcontentView.contentMode = UIViewContentModeRedraw;
That gives me the correct end result (my custom view redraws with the new size, and is laid out properly, like the first image I posted), but the animation of the transition is unpleasant: it looks like the cell stretches and shrinks back to size.
I know why the animation is working like that: Core Animation doesn't ask your view to redraw for each frame, it gets it to redraw for the end position of the animation and then interpolates to find the middle bits.
Another solution is to do
subcontentView.contentMode = UIViewContentModeLeft;
That just draws the delete button over my cell, so it covers part of it.
If I also implement
- (void) didTransitionToState:(UITableViewCellStateMask)state
{
[self setNeedsDisplay];
}
then once the delete button has slid in the cell 'jumps' to the correct size. That way there's no nice slidey animation, but at least I get the correct result at the end.
I guess I could run my own animation in parallel with the delete button appearing, temporarily creating another view with a copy of the image of my view in the old size, setting mine to the new size, and fading between them — that way there would be a nice cross fade instead of a sharp jump. Anyone use such a technique?
Now, you might ask why I can't use the contentStretch property and give it a region to resize. The problem with that is I'm making something to be reasonably generic, so it's not always going to be possible. In this particular example it'd work, but a more complex cell may not.
So, my question (after all of this background) is: what do you do in this situation? Does anyone have the animating delete button working for custom drawn cells? If not, what's the best compromise?
This worked for me finally. in subclass of UITableViewCell
subDrawContentView.contentMode = UIViewContentModeLeft;
overide layout subviews method
- (void)layoutSubviews {
CGRect b = [subDrawContentView bounds];
b.size.width = (!self.showingDeleteConfirmation) ? 320 : 300;
[subDrawContentView setFrame:b];
[subDrawContentView setNeedsDisplay];
[super layoutSubviews];
}
So I will paste the code first and then I will explain:
-(void)startCancelAnimation{
[cancelButton setAlpha:0.0f];
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
cancelButton.hidden=NO;
[UIView animateWithDuration:0.4
animations:^(void){
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, 159.0, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, 159.0, text.frame.size.height)];
[cancelButton setFrame:CGRectMake(244., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:1.0f];
} ];
}
-(void)stopCancelAnimation{
[UIView animateWithDuration:0.4
animations:^(void){
[cancelButton setFrame:CGRectMake(320., cancelButton.frame.origin.y, cancelButton.frame.size.width, cancelButton.frame.size.height)];
[cancelButton setAlpha:0.0f];
}completion:^(BOOL completion){
cancelButton.hidden=YES;
[cancelButton setAlpha:1.0f];
[progressView setFrame:CGRectMake(progressView.frame.origin.x, progressView.frame.origin.y, DEFAULT_WIDTH_PROGRESS, progressView.frame.size.height)];
[text setFrame:CGRectMake(text.frame.origin.x, text.frame.origin.y, DEFAULT_WIDTH_TEXT, text.frame.size.height)];
}
];
}
-(void)decideAnimation{
if([cancelButton isHidden]){
[self startCancelAnimation];
}
else{
[self stopCancelAnimation];
}
}
So what I have there is a button that looks like this:
I have an IBOutlet to it. And what I am doing is resizing a UIProgressView and a UITextField (you can resize whatever you want). As for the code is pretty simple, but if you need any help to understand what's going on, please ask. Also, don't forget to add the Swip Gesture to the UITableView... Like this:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(didSwipe:)];
gesture.numberOfTouchesRequired=1;
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[table addGestureRecognizer:gesture];
[gesture release];
Finally the method that does it all:
-(void)didSwipe:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
//Get the cell of the swipe...
CGPoint swipeLocation = [gestureRecognizer locationInView:self.table];
NSIndexPath *swipedIndexPath = [self.table indexPathForRowAtPoint:swipeLocation];
UITableViewCell* swipedCell = [self.table cellForRowAtIndexPath:swipedIndexPath];
//Make sure its a cell with content and not an empty one.
if([swipedCell isKindOfClass:[AMUploadsTableViewCell class]]){
// It will start the animation here
[(AMUploadsTableViewCell*)swipedCell decideAnimation];
// Do what you want :)
}
}
}
So as you can see the whole animation is created manually, so you can control exactly what you want. :)