State of code:
I'm writing my own library for forms in table view (I'm aware of Sensible Cocoa but willing to write my own) and I'm dealing with an issue loosely connected to it.
My form is held by UITableViewController in UITableViewStyleGrouped style. This table view controller is created in a button handler with this code (initialization of form model ommited):
// prepare table view model
_formTableViewController = [[ESCTableViewController alloc] initWithModel:model];
_formTableViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel
target:self action:#selector(cancelBarButtonItemHandler:)];
_formTableViewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemEdit
target:self action:#selector(editBarButtonItemHandler:)];
// prepare navigation controller containing the tableViewController
ESCNavigationController *navigationController = [[ESCNavigationController alloc] initWithRootViewController:_formTableViewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
// present navigation view controller
[self presentViewController:navigationController animated:YES completion:nil];
Then, I have UITableViewCell subclassed (as ESCTableViewCell) with style UITableViewCellStyleDefault and each form component is a descendant of ESCTableViewCell using its basic initializer and few properties.
I'm doing my custom rendering like this:
Create my custom field (i.e. UITextField) in initializer of specific cell (ESCTextFieldCell)
prepareForReuse is called by framework on cell reuse, where I remove old observers and invalidate what is needed to invalidate
configWithModel:(Model *)model, where I set properties of the cell, add observers etc.
layoutSubviews is called by framework; meta-code of this process is here:
CGFloat const ESCTextFieldWidthRatio = 0.65f;
CGFloat const ESCFieldHeight = 22.0f;
CGFloat const ESCFieldPadding = 10.0f;
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
// call super layout subviews first
[super layoutSubviews];
// count width of the text field, so it would fit with label
// must round() the results to avoid antialiasing on fractional values
CGFloat contentWidth = self.contentView.frame.size.width;
CGFloat contentHeight = self.contentView.frame.size.height;
CGFloat fieldWidth = round((contentWidth - (2 * ESCFieldPadding)) * ESCTextFieldWidthRatio);
CGFloat fieldOriginY = round((contentHeight / 2) - (ESCFieldHeight / 2));
CGFloat labelWidth = contentWidth - fieldWidth - (4 * ESCFieldPadding);
// set textlabel and textfield frames
self.textLabel.frame = CGRectMake(ESCFieldPadding, 0.0f, labelWidth, contentHeight);
self.textField.frame = CGRectMake(labelWidth + (3 * ESCFieldPadding), fieldOriginY, fieldWidth, ESCFieldHeight);
[UIView commitAnimations];
The Problem:
I'm trying to have the UI as clean as possible, being a perfectionist; thus I want to have table view cells animated on entry/leave of edit mode, which works pretty well in current state of code.
The issue is with presenting the modal view itself with this form table view inside. It seems (proven by simulator - slow animations), that when presenting the modal view, frame of the table view is updated (contracted) by (at least) a few pixels, which is causing both modal view translation animation AND table view cell resize animation, which is at the end causing a not-so-smooth animation of that modal view presentation.
What I would like to understand is, why the table view cell is resizing on modal view presentation as I don't like the idea to hack the code to disable animations when presenting the modal view (it can possibly cause lots of other issues later).
Please, does someone know why this is happening, or do you have some other idea how to override it non-hacky way?
Thank you a lot!
PS: I'm using ARC, but I don't think it matters.
PS2: Problem occures on both iPad1 and iOS Simulator for iOS5.
Miroslav, I'm not sure this helps, but I've been fighting the same thing. And I'm sharing this here because I assume others might wonder what's happening.
It's not just the cell, it's the entire tableview bounds that aren't settled until ViewDidAppear is called.
So let's say you want to bring up a 500x500 modal with a UITableViewController, you launch it with presentViewController, you will have all kinds of resizing going on until it's finally resolved in ViewDidAppear. In my case I've seen it through debugging go from 1024 wide to 320 then finally to 500. And it's that width that I need to determine the resulting height of the table view cells.
It all seems like a hack in this case and I'm not sure there is a clean solution though I wish for one. One way in which I'm testing it now, is that the custom table view cells have a "final width" property that they look at (if it's set) when sizing in layoutSubviews. That way the proper height of the cell can be calculated (based on this fixed width) at anytime prior to the tableview being in it's final form.
I've seen the same issue in some original (read Apple) applications on iOS, so I consider it a feature of the system. It seems, the best we can do, is to hide it as much as possible. Actually, nobody except me noticed the behavior, so I think nobody is bothered.
Consider it not an answer, but a closure. If you have some real solution for the problem, please write it here and I'll mark it as an answer.
Set the UITableView layout to delete outside autosizing.
Related
So I realized this myself and found this answer to confirm:
Prevent contentSizeForViewInPopover from animating
Basically I am in a similar boat in that I want to dynamically resize my popover, depending on how much data I have to display. I am also getting this animation action where the popover view moves into place. The problem is, I can't set the popover's content size in the caller/parent, because neither the size of the content view for the text view nor the frame for the text view in my popover is known until viewDidAppear or viewDidLayoutSubviews is called, but of course at this point it's too late, the popover is already on its way to being visible, thus I am getting this unwanted animation.
I am dynamically setting the size of the text views in the popover view controller class to fit the amount of data the text views are displaying, and this only works at the point where their frame size is defined/known/however you want to word it when it hasn't gotten to that point yet in the view lifecycle.
I should mention that I have my view controllers and view all done in a storyboard.
Does this make sense? Hope I'm explaining it well. Any help or suggestions greatly appreciated.
EDIT Here is my viewDidLayoutSubviews method from the view controller I am loading in the popover - hopefully this makes it clear. The first time this loads, if I don't do this in viewDidLayoutSubviews or viewDidAppear the frame for my 2 text views are CGRectZero, the text views don't resize for the content correctly and the popover size isn't right either:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.physicianDataNewValueTextView.text = self.physicianDataNewValue;
CGRect frame = self.physicianDataNewValueTextView.frame;
frame.size.height = self.physicianDataNewValueTextView.contentSize.height;
self.physicianDataNewValueTextView.frame = frame;
self.physicianDataOldValueTextView.text = self.physicianDataOldValue;
frame = self.physicianDataOldValueTextView.frame;
frame.size.height = self.physicianDataOldValueTextView.contentSize.height;
self.physicianDataOldValueTextView.frame = frame;
self.contentSizeForViewInPopover = CGSizeMake(384.0f, self.physicianDataOldValueTextView.frame.size.height + 75.0f);
}
So I decided to use delegation. Instead of resetting the content size in the view controller for the popover, I call my delegate (which is the containing view controller) with the new size so I can set it there with the animated property set to NO. Works well.
I understand how to implement UISwipeGestureRecognizer, but I do not understand how to use multiple views properly in collaboration with it.
My ultimate goal is to provide a sleek interface where buttons or swiping may be used to change views. I am hoping for a constant background rather than a background image show it is transferring to the exact same image again. I am using storyboarding currently where each view has its own controller, and I would like (if possible) to keep it as close to this format for visual clarity of what is going on.
I have read that subviews may be my best hope for this sort of scenario (keeping them to the right until a swipe is called), but that would break up the current storyboarding structure I am using, and most certainly ruin any visual clarity.
Does anyone have any suggestions?
I did same programmatically, but you can achieve same using storyboard
1. You need base view which will keep same background
2. Add 3 Views (or 2 depending on ur requirement) Left, Middle, Right with transparent backgrounds as follows in viewDidLoad of base view controller
[self addChildViewController:myChildViewController];
[myChildViewController view].frame = CGMakeRect(x,y,width,height);//Left one will have x = -320, y=0 ,Middle x= 0, y= 0, Right x = 32O etc.
/* Add new view to source view */
[self.view addSubview:[myChildViewController view]];
/* Inform 'didMoveToParentViewController' to newly added view controller */
[myChildViewController didMoveToParentViewController:self];
3. In handle Swipe method change frames of view with animation which will make it look like swiping page
e.g
//swipe left, get right view in middle
[UIView animateWithDuration:0.2 animations:^{
CGRect frame = rightViewController.view.frame;
frame.origin.x = 0;
frame.origin.y = 0;
rightViewController.view.frame = frame;
CGRect middleViewFrame = middleViewController.view.frame;
middleViewFrame.origin.x = -320;
middleViewController.view.frame = middleViewFrame;
}];
You could use a navigation to do this. If you don't want the navigation bar, you can hide it. You can connect the left swipe gesture recognizer to a segue to the next controller, and connect the right swipe gesture recognizer to an unwind segue (or call popViewControllerAnimated: from its action method). If you want a constant background, then you need to add that to the navigation controller's view (as a background color), and have the child view controller's views be transparent (or partially so). This code in the navigation controller's root view controller will add the background:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"img.tiff"]];
}
I know exactly what you're talking about and I'm actually doing the same with my current project.
I accomplished this by using Interface Builder and a single view controller. Basically I placed a UIImageView in the view and set it to the background you wish, then I put a transparent UIScrollView on top of it. Inside that scroll view I put the two views I want to scroll horizontally through.
Don't forget to enable UIScrollView paging. In my case, I had two table views. Starting from the first one, if you swiped from right to left it would scroll to the next table view, and if you swiped left to right it would scroll back to the first.
I have an iphone app with 2 ViewControllers . Both screens(viewcontrollers) show a loading screen. I create the loading screen programmatically:
UIView *loadingScreen = [[UIView alloc] initWithFrame:CGRectMake(100,200,144,144)];
loadingScreen.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
//{.. Other customizations to loading screen
// ..}
[self.view addSubview:loadingScreen];
For some reason, the second viewcontroller's loadingScreen is significantly lower and it isn't centered on the screen. The first viewcontroller works perfectly and is dead center like I want.
The second viewcontroller is a UITableView and it shows the uinavigationbar, whereas the first viewcontroller doesn't show the uinavigationbar. Also, I use storyboard for my app.
I've outputted to the NSLog self.view.frame.size.height and loadingScreen.center in both instances and THEY HAVE THE SAME COORDINATES! So, not sure why it is showing up lower. Any ideas why the second loadingScreen is lower and how to fix? Thanks!
You mention that one screen displays a UINavigationBar while the other does not. When you display a navigation bar, it offsets the rest of your view - in this case by shifting it down.
There are two quick fixes. You can either adjust your center point up by the size of the UINavigationBar (65 pts - unless it's a custom UINavigationBar and you've changed its size) or you can set the "Adjust Scroll View Insets" value to false in the attributes inspector.
The latter is probably the easiest and comes most recommended. Note though, that the top of your UITableView will now be underneath the UINavigationBar.
My final note would be that if you wanted to do it programmatically than in your UITableView's delegate you can call
- (BOOL)automaticallyAdjustsScrollViewInsets
{
return NO;
}
So I realized this myself and found this answer to confirm:
Prevent contentSizeForViewInPopover from animating
Basically I am in a similar boat in that I want to dynamically resize my popover, depending on how much data I have to display. I am also getting this animation action where the popover view moves into place. The problem is, I can't set the popover's content size in the caller/parent, because neither the size of the content view for the text view nor the frame for the text view in my popover is known until viewDidAppear or viewDidLayoutSubviews is called, but of course at this point it's too late, the popover is already on its way to being visible, thus I am getting this unwanted animation.
I am dynamically setting the size of the text views in the popover view controller class to fit the amount of data the text views are displaying, and this only works at the point where their frame size is defined/known/however you want to word it when it hasn't gotten to that point yet in the view lifecycle.
I should mention that I have my view controllers and view all done in a storyboard.
Does this make sense? Hope I'm explaining it well. Any help or suggestions greatly appreciated.
EDIT Here is my viewDidLayoutSubviews method from the view controller I am loading in the popover - hopefully this makes it clear. The first time this loads, if I don't do this in viewDidLayoutSubviews or viewDidAppear the frame for my 2 text views are CGRectZero, the text views don't resize for the content correctly and the popover size isn't right either:
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.physicianDataNewValueTextView.text = self.physicianDataNewValue;
CGRect frame = self.physicianDataNewValueTextView.frame;
frame.size.height = self.physicianDataNewValueTextView.contentSize.height;
self.physicianDataNewValueTextView.frame = frame;
self.physicianDataOldValueTextView.text = self.physicianDataOldValue;
frame = self.physicianDataOldValueTextView.frame;
frame.size.height = self.physicianDataOldValueTextView.contentSize.height;
self.physicianDataOldValueTextView.frame = frame;
self.contentSizeForViewInPopover = CGSizeMake(384.0f, self.physicianDataOldValueTextView.frame.size.height + 75.0f);
}
So I decided to use delegation. Instead of resetting the content size in the view controller for the popover, I call my delegate (which is the containing view controller) with the new size so I can set it there with the animated property set to NO. Works well.
I have a UITextview, becomes first responder and keyboard is presented. Presently, I have buttons in inputAccessoryView toolbar that exchange the text forward and reverse through an array of strings.
I have the process working with no issues, so of course I am inclined to break it. My wish is to slide the textview left and right like a carousel to make it more clear to the user that next or previous string is coming and going. The current system simply replaces the text with no animation.
My first thought was to create a UINavigationController, give it an array of UIViewControllers that present the UITextviews. The navigation controllers view is only as big as the textview and I add it as a subview to my full view (which is itself in a navigation controller). I got this working fairly completely, the navigation bar is hidden and it looks no different than the original textview except that the textview now slides off to the right or left, depending if I am pushing or popping.
The problem with that is that the keyboard slides off along with the dismissed view controller, then the new textview in the new controller becomes first responder and the keyboard returns. Close, but no cigar.
I considered using page view controller but it seems it will have the same issue. I think I may have to go back to the single textview and animate the whole process directly with static screen grabs. That is completely beyond my experience level and I am think there must be a simpler way.
Can anyone suggest a simple way to keep that keyboard present while the views are swapped as described? Suggestions on other angles of attacking this?
Seems like an awful lot of overhead for a simple animation.
Try something like this (assuming ARC):
typedef enum _eDirection
{
rightToLeft = -1,
leftToRight = 1
} eDirection;
- (void) animateTextFields:(eDirection)direction
{
CGFloat distance = self.window.bounds.size.width;
UITextField *oldTextField = thisView.textField;
CGRect oldTFRect = oldTextField.frame;
CGRect newTFRect = CGRectOffset(oldTFRect, -direction * distance, 0);
UITextField *newTextField = [[UITextField alloc] initWithFrame:newTFRect];
[newTextField setText:#"whatever"];
[self addSubview:newTextField];
[UIView animateWithDuration:0.3f
animations:^{
[oldTextField setFrame:CGRectOffset(oldTextField.frame, direction * distance, 0)];
[newTextField setFrame:CGRectOffset(newTextField.frame, direction * distance, 0)];
} completion:^(BOOL finished) {
[self setTextField:newTextField];
[self removeSubview:oldTextField];
[newTextField becomeFirstResponder];
}];
}
(Disclaimer: as with all code typed off the top of one's head, etc., etc.)
This method would create a new textField, animate it horizontally onto the screen while moving the existing one off the screen in the direction you give, and then assigns the new textField as the current one and makes it the firstResponder.