Switch from animating frames to animating constraints - ios

I had what seemed a perfectly good animation scheme (animating frame attributes using UIView animateWithDuration) until an upgrade to Xcode 6 or iOS 8 (or both) changed everything. I suspect that part of the problem is that I paid scant attention to AutoLayout because the app only supports portrait view.
A picture is worth a thousand words, and a video is worth a million, so a screen capture example of this change in animation can be seen in the following youtube uploads:
The first example (iOS 7) graphically shows what I'm trying to achieve, and the second shows what happened after I switched to Xcode 6 / iOS 8. Please view if you want to respond:
Here's my current (simplified) code, which is inside a fast enumeration loop iterating over the fetchedResults of a FRC:
[UIView animateWithDuration:.8
delay:0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^
{
// Starting state *************************************
thisBar.frame = CGRectMake(20, self.scroller.contentSize.height, 130, 0);
thisBar.backgroundColor = [UIColor blackColor];
thisInfoLabel.frame = CGRectMake(20, self.scroller.contentSize.height, thisBar.frame.size.width, 30);
thisTimeLabel.frame = CGRectMake((thisBar.frame.origin.x) + (thisBar.frame.size.width + 35), self.scroller.contentSize.height, 150, 15);
thisTimeLabel.textColor = [UIColor blueColor];
arrowView.frame = CGRectMake(thisTimeLabel.frame.origin.x - 20, thisTimeLabel.frame.origin.y, 16, 16);
thisInfoLabel.textColor = [UIColor clearColor];
// End state *************************************
thisBar.frame = CGRectMake(20, barY, 130, - barHeight);
thisBar.backgroundColor = thisBar.endColor;
thisBar.layer.shadowColor = [[UIColor blackColor] CGColor];
thisBar.layer.frame = CGRectInset(thisBar.layer.frame, 0.0, 3.0);
thisBar.layer.shadowOpacity = 0.7;
thisBar.layer.shadowRadius = 4.0;
thisBar.layer.shadowOffset = CGSizeMake(5.0f, 5.0f);
...
}
I've read various possible reasons for this change, but the detail appears to be moot, and apparently the only proper way to fix the problem is to switch from animating the frame attributes of my views and start animating constraints instead.
OK, I accept it. However, as I've looked at quite a few SO posts on the subject for guidance, most seem to be geared toward IB-generated views. My views are code generated, of any number, and of any height, both of which are determined on the fly in code. For added drama, these views are inside a UIScrollView.
At this point, it seems my strategy should be (per view):
1) Create the view
2) Set up constraints for each view
3) Animate the constraints for each view, sequentially
Anybody have any suggestions or pointers to helpful tutorials? A simple example relating several code-generated views to each other, particularly inside a UIScrollView would be a godsend.
Many thanks!

Related

Set CALayer as SCNMaterial's diffuse contents

I've been searching all over the internet over the past couple of days to no avail. Unfortunately, the apple documentation about this specific issue is vague and no sample code is available (at least thats what I found out). What seems to be the issue you may ask...
I'm trying to set a uiview's layer as the contents of the material that is used to render an iPhone model's screen (Yep, trippy :P ). The iPhone's screen's UV mapping is set from 0 to 1 so that no issue persists in mapping the texture/layer onto the texels.
So, instead of getting this layer to appear rendered on the iPhone, same as left image, Instead, I get this rendered onto the iPhone like right image
Correct Render                                        Incorrect Render
Also note, that when I set a breakpoint and debug the actual iPhone node and view it in Xcode, a completely different render is shown and the layer gets half-fixed when I continue execution:
Now then... HOW do I fix this issue??? I've tried playing with the diffuse's contents transform matrix but nothing gets fixed. I've also tried resizing the UIView to 256x256 (since the UV seems to be 256x256 as shown in blender - the 3d modelling package), but that doesn't fix anything.
Here is the code for the layer:
UIView *screen = [[UIView alloc] initWithFrame:self.view.bounds];
screen.backgroundColor = [UIColor redColor];
UIView *temp = [[UIView alloc] initWithFrame:CGRectMake(0, 0, screen.bounds.size.width, 60)];
temp.backgroundColor = [UIColor colorWithRed:0 green:(112.f/255.f) blue:(235.f/255.f) alpha:1];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectInset(temp.bounds, 40, 0)];
label.frame = CGRectOffset(label.frame, 40, 0);
label.textColor = [UIColor colorWithRed:0 green:(48.f/255.f) blue:(84.f/255.f) alpha:1];
label.text = #"Select Track";
label.font = [UIFont fontWithName:#"HelveticaNeue-Light" size:30];
label.minimumScaleFactor = 0.001;
label.adjustsFontSizeToFitWidth = YES;
label.lineBreakMode = NSLineBreakByClipping;
[temp addSubview:label];
UIView *separator = [[UIView alloc] initWithFrame:CGRectMake(0, temp.bounds.size.height - 2, temp.bounds.size.width, 2)];
separator.backgroundColor = [UIColor colorWithRed:0 green:(48.f/255.f) blue:(84.f/255.f) alpha:1];
[temp addSubview:separator];
[screen addSubview:temp];
screen.layer.contentsGravity = kCAGravityCenter;
Edit
What's even weirder is that if I capture a UIImage of the view using:
- (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
and use that as the diffuse's content... everything works out perfectly fine?! It's really weird and frustrating since the image's size is exactly the same as the uiview's...
Edit 2
I ended up just using an image of the view as the texture, which makes things much more static than I needed. I won't set this as the answer because I'll still be waiting for a correct fix to this issue even if it in a long time. So, if you have an answer and this topic has been opened for a long time, please bump it if you can. The documentation on this section is just so poor.
New post on an old thread, but this day-in-age, it's possible to set the UIView itself as SCNMaterialProperty (diffuse) contents. Intention to support this feature is communicated directly from SceneKit engineering at Apple, though the documentation has not yet been updated to reflect it.
To tied back to the original post, do not set a UIView.layer as material property contents; instead set contents to the UIView itself.
[Update: according to Lance's comment below, support for views may be getting worse rather than getting better.]
The SceneKit docs pretty strongly suggest that, while there are cases where you can use animated CALayers as material content, that doesn't include UIView layers:
SceneKit cannot use a layer that is already being displayed elsewhere (for example, the backing layer of a UIView object).
That suggests that if you want to make animated content for your material, you're better off with either Core Animation used entirely on its own or SpriteKit.

How to make sharp look of views in Scroll view on zooming

I am working on application where I have a bunch of views (UILabel, UITextField, UIButton and etc). I enabled zooming and every thing working fine, but only one thing which is not good when user zoom in and want to look any view its gets pixelated and looking quite blurry. One thing which may be the reason I am using quite low font size as you may in below code, and I am not able to use high font because I am bound to use many views because to add many view in iphone litter size.
UITextField *textField = [[UITextField alloc]init];
rect.origin.x = rect.origin.x + 3;
textField.frame = rect;
textField.backgroundColor = [UIColor whiteColor];
[textField setFont:[UIFont systemFontOfSize:5.0]];
textField.tag = fieldsYValue;
textField.adjustsFontSizeToFitWidth = NO; // this is default value
[scrollView addSubview:textField];
Looking for any help who worked on this kind of projects.
Maybe this could help.
This lets the user zoom in by a factor of 3 -- but when they do so, they're really just zooming up to full resolution, at which 1 pixel in the UIScrollView's content buffer is 1 pixel on screen. (And of course, within our content view, we draw everything three times bigger to compensate.) No need to implement any of the delegate methods except the standard viewForZoomingInScrollView; no redrawing at different scales depending on the zoom.
scrollView.contentSize = myContentView.bounds.size;
scrollView.maximumZoomScale = 1.0;
scrollView.minimumZoomScale = 0.33;
scrollView.zoomScale = 0.33;

Why would an activity indicator show up properly on iPhone but not on iPad?

I have my app setup to show this view when it is loading data:
self.loadingView = [UIView new];
self.loadingView.frame = CGRectMake(0, 0, self.tableView.frame.size.width, self.view.frame.size.height);
self.loadingView.backgroundColor = [UIColor groupTableViewBackgroundColor];
[self.view addSubview:self.loadingView];
[self.view bringSubviewToFront:self.loadingView];
self.activityIndicator = [UIActivityIndicatorView new];
self.activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
self.activityIndicator.center = CGPointMake(self.view.frame.size.width / 2.0, self.view.frame.size.height / 2.0);
[self.view addSubview:self.activityIndicator];
[self.view bringSubviewToFront:self.activityIndicator];
[self.activityIndicator startAnimating];
Then, I remove it from its superview. It works on iPhone. It works on iPad sometimes too, except for when I'm using the same code in a UISplitViewController. I've tried various adjustments to centering the views, etc., but can't figure it out. What's going wrong?
I've add trouble with activity indicators in the past as well. Make sure you are not calling the startAnimating or stopAnimating while any animations are taking place. I recommend calling the startAnimating selector in viewDidLayoutSubviews.
The line where you set the center of the indicator looks like the source of the problem. self.view.frame.size is probably equal to screen size at this point and so when you show that view controller over the whole screen it's ok, but inside a split view controller it's not because indicator is off-bounds. You can check that from Xcode's Debug -> View Debugging -> Capture View Hierarchy (while the app is running).
Try setting activity indicator's center using autolayout and it should work.

ios squaring edges in UIProgressView

I'm writing a UI for iPad and part of that UI needs progress bars for certain parts. However, I'm finding that configuring the UIProgressView is proving difficult. One of the requirements is that the endpoints of the progress view be square, however, I am unable to configure this via the layer's corner radius property:
fProgressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
fProgressView.layer.borderColor = [UIColor whiteColor].CGColor;
fProgressView.progress = 0.5f;
fProgressView.progressImage = nil;
fProgressView.trackImage = nil;
fProgressView.trackTintColor = self.fPatientListPanelColor;
fProgressView.progressTintColor = [UIColor whiteColor];
[fProgressView.layer setCornerRadius:1.0f];
[self.view addSubView:fProgressView]
I find I am also unable to control the height of it. Any thoughts on how to overcome these issues?
There is apparently no way to do this, so I ended up rolling my own version.

elegant (i.e., non-hard-coded) way to position `UITextField`, `UITextView`, and `UIButton` relative to each other?

I have the following screen in my app.
Right now the positioning is all hard-coded, and it's not pretty. It's one textview with several \ns in the middle and a textfield and button carefully positioned, experimenting pixel by pixel, and then hard-coded in, which is fine except then if I switch to a 4-inch screen it's useless. Plus it's just ugly.
I've been looking around stackoverflow trying to find answers, and I found some things about creating a CGPoint at a specific UITextPosition, but unfortunately I'm too much a novice to understand the answers.
Is there an elegant way to soft-code these positions relative to each other?
Thanks for your help.
EDIT: Here's the positioning code:
if (!optOut) {
optOut = [[UITextView alloc] initWithFrame:CGRectMake(20, 95, [[UIScreen mainScreen] bounds].size.width - 20, 200);
optOut.backgroundColor=[UIColor clearColor];
optOut.text = #"\n\n\nI hope to update the Haiku app periodically with new haiku, and, if you'll allow me, I'd like permission to include your haiku in future updates. If you're okay with my doing so, please enter your name here so I can give you credit.\n\n\n\nIf you DON'T want your haiku included \nin future updates (which would make \nme sad), check this box.";
}
[self.view addSubview:optOut];
if (!checkboxButton) {
checkboxButton = [UIButton buttonWithType:UIButtonTypeCustom];
checkboxButton.frame = CGRectMake(236, 260, 44, 44);
[self.view addSubview:checkboxButton];
}
[textView resignFirstResponder];
if (!nameField)
{
nameField=[[UITextField alloc] initWithFrame:CGRectMake(40, 223, 240, 30)];
[self.view addSubview:nameField];
}
Instead of placing optOut at (20, 95, [[UIScreen mainScreen] bounds].size.width - 20, 200), I'd like to be able to put it at (say) (20, [[UIScreen mainScreen] bounds].size.height/2-100, [[UIScreen mainScreen] bounds].size.width - 20, 200). But if I do that--if the starting position of optOut moves depending on how tall the screen is--then I have to move nameField and checkBox too, and I don't know how to keep them in the same position relative to optOut.
So first, I would put the second part of that UITextView (after all the returns) into its own object and tack it on to the view by itself. And then the simplest way to go about this with what you have is to ask the elements directly for frame placement, so where you have
checkboxButton.frame = CGRectMake(236, 260, 44, 44);
do something like (and I apologize I if the syntax is a bit off but you can get the idea)
checkboxButton.frame = CGRectMake([optOut center].y + pixelsToMoveOverToWhereYouLikeIt, [optOut center].y + pixelsToMoveDownToWhereYouLikeIt , 44, 44);
And so on for the others. That would make all the view either relative to each other or to one the first textview, depending on which object you reference.
It would be a lot easier to break it apart into different components that you can position individually, and either use constraints to automatically position them or move them individually in code. Also, a UILabel would be better to display the static text, since you don't need the user to be able to edit the text. Something like this:
There are basically two ways you could do this. The first is to use auto-layout. I don't have any experience with auto-layout because it is an iOS 6+ feature, but it would probably be worth learning.
The way I usually do it would be be something like:
view1.frame = CGRectMake(20, 20, 200, 200);
view2.frame = CGRectOffset(view1.frame, 0, 20); // same as view1, offset down 20 points
view3.frame = CGRectMake(CGRectGetMinX(view2.frame), CGRectGetMaxY(view2.frame)+10, 80, 80);
These CGGeometry helper functions are quite useful. There are more obscure CGGeometry functions that are less useful, but can be great in some situations: http://nshipster.com/cggeometry/ For the full list, just check out the CGGeometry documentation
The text field looks indeed ugly there. You want an UITextField of type custom with a nice self styled image as backgound. That way you can postion the text as you want. Do the same with the button of type custom, use 4 different images for the 4 states (unchecked, unchecked-pressed, checked and checked-pressed).

Resources