UIView with rotation not resizing - ios

I've created UIView Containing UILabel inside (as suggested in: AutoLayout with rotated UI Elements). The problem is that it does not want to autolayout when I rotate it by 90 degrees (see pictures).
Image before rotation:
and after:
Rotation is done by:
yTitleView.translatesAutoresizingMaskIntoConstraints = NO;
yTitleLabel.transform = CGAffineTransformMakeRotation (-3.14/2);
Constraints between SleepScoreUILabel and blue UIView are just to keep some distance between them:
EDIT1:
Tried with suggestions of #bennegeek
yTitleLabel.translatesAutoresizingMaskIntoConstraints = YES;
CGRect frame = yTitleLabel.frame;
bottom.active = NO;
top.active = NO;
leading.active = NO;
trailing.active = NO;
yTitleLabel.transform = CGAffineTransformMakeRotation (-M_PI/2);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
yTitleLabel.frame = frame;
});
But the result is:
If after those operations I turn the constraints back active, I get:

Related

Custom UIDynamicBehavior: how do I know the total number of animation steps/ticks?

I'd like to implement a custom UIDynamicBehavior that makes a view "burst". To do that I need to fade it out and scale it to 2x its size.
I do this by setting the view's alpha and bounds in the action block. However, how do I know how often the action block is called? The docs say "on each tick", but how many?
I added a counter. With no other animations, the block is called 30 times. With a few gravity and dynamic behaviors, it is called 500 times.
I also don't see how the UIDynamicAnimator knows when its behaviors are "done" moving stuff around. Can anyone shed some light on this?
The code below works sometimes, but other times the behavior stops before the view is animated completely (i.e. it is still visible).
self.action = ^{
static NSInteger count = 0;
NSLog(#"animation tick: %d", count);
count++;
UIView *view = (UIView*)[weakSelf.items lastObject];
view.alpha = view.alpha - 0.1;
CGRect bounds = view.bounds;
bounds.size.width += 1;
bounds.size.height += 1;
view.bounds = bounds;
};
For detecting when the animation has finished you could try this:
__weak Entity *weakSelf = self;
self.behavior.action = ^{
if (weakSelf.center.x == weakSelf.lastPosition.x && weakSelf.center.y == weakSelf.lastPosition.y) {
NSLog(#"end of dynamic movement");
}
weakSelf.lastPosition = weakSelf.center;
};
Or check another value that you are changing.

UIWebView content not adjusted to new frame after rotation

I have an e-mail application,
which shows the content in an UIWebView inside an UISplitViewController.
Everything works fine until i rotate the device, when already zoomed in/out in the UIWebView.
When rotating the device, i adjust the frame of the UIWebView in
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
The problem is, when i zoomed in/out in the UIWebViewand then rotate the device.
The content isn't resized to it's new frame, which leads to a gray/black border in the content, or the content can be scrolled horizontal.
Addional information:
The UIWebViewis a subview on a UIScrollView which can be scrolled.
When the UIWebViewis totally visible the scrollEnabledof the UIScrollViewis disabled and the UIWebView can be scrolled.
Above picture shows the UIWebViewin landscape when selecting an email.
This is the state before rotating in both cases.
[Not zoomed before rotating]Above picture shows that the UIWebView is correctly resizing the content to it's new frame.
[Zoomed before rotating]Above picture shows the UIWebView after zooming in landscape and then rotating to portrait
-> content isn't correctly shown
Information of the UIWebView.scrollView in - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation:
<_UIWebViewScrollView: 0xb1322a0; frame = (0 0; 768 960); clipsToBounds = YES; autoresize = H; gestureRecognizers = <NSArray: 0xb1321d0>; layer = <CALayer: 0xb132270>; contentOffset: {0, 0}>
frame is correct!
Edit: Solution!!
i managed to solve it by updating the viewport width after rotating the device
CGFloat fll_width = self.view.frame.size.width;
NSString* adsl_javascript_string = [NSString stringWithFormat:#"var setViewPortScript = document.createElement('meta');\
setViewPortScript.setAttribute('name', 'viewport');\
setViewPortScript.setAttribute('content', 'width = %f');\
document.getElementsByTagName('head')[0].appendChild(setViewPortScript);", fll_width];
[adsc_webview stringByEvaluatingJavaScriptFromString:adsl_javascript_string];
i managed to solve it by updating the viewport width after rotating the device
CGFloat fll_width = self.view.frame.size.width;
NSString* adsl_javascript_string = [NSString stringWithFormat:#"var setViewPortScript = document.createElement('meta');\
setViewPortScript.setAttribute('name', 'viewport');\
setViewPortScript.setAttribute('content', 'width = %f');\
document.getElementsByTagName('head')[0].appendChild(setViewPortScript);", fll_width];
[adsc_webview stringByEvaluatingJavaScriptFromString:adsl_javascript_string];
This seems to be a bug also in iOS 7 and iOS 8.
A fix would be this:
First resize the frame on layout changes:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
_webView.frame = self.view.bounds;
}
Then on the rotation callback reset the zoomScale to 0. Yes, 0!
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
// Allow the animation to complete
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[_webView.scrollView setZoomScale:0 animated:YES];
});
}
You can also use the new function
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
// Allow the animation to complete
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[_webView.scrollView setZoomScale:0 animated:YES];
});
}

Horizontally center multiple UIViews

I want to horizontally center a number of UIViews (they happen to be circles) in the master UIView. It will end up basically looking like the dots on the standard Page Control.
I have all the code written to create the circle UIViews I just have no idea how to arrange them horizontally and dynamically at run time.
Essentially I need some kind of horizontal container where I can do this
-(void)addCircle{
[self addSubView:[CircleView init]];
}
And it will auto arrange however many children it has in the center.
I get confused with auto-layout as well from time to time but here is a way how you can do it programmatically: (I assume that you add your circle views to a containerView property of your view controller and you do not add any other views to it.)
Add these two properties to your view controller:
#property (nonatomic) CGRect circleViewFrame;
#property (nonatomic) CGFloat delta;
Initiate those properties with the desired values in your view controller's viewDidLoad method:
// the size (frame) of your circle views
self.circleViewFrame = CGRectMake(0, 0, 10, 10);
// the horizontal distance between your circle views
self.delta = 10.0;
Now we add your "automatic addCircle method":
- (void)addCircleView {
UIView *newCircleView = [self createCircleView];
[self.containerView addSubview:newCircleView];
[self alignCircleViews];
}
Of course we need to implement the createCircleView method...
- (UIView*)createCircleView {
// Create your circle view here - I use a simple square view as an example
UIView *circleView = [[UIView alloc] initWithFrame:self.circleViewFrame];
// Set the backgroundColor to some solid color so you can see the view :)
circleView.backgroundColor = [UIColor redColor];
return circleView;
}
... and the alignCircleViews method:
- (void)alignCircleViews {
int numberOfSubviews = [self.containerView.subviews count];
CGFloat totalWidth = (numberOfSubviews * self.circleViewFrame.size.width) + (numberOfSubviews - 1) * self.delta;
CGFloat x = (self.containerView.frame.size.width / 2) - (totalWidth / 2);
for (int i = 0; i < numberOfSubviews; i++) {
UIView *circleView = self.containerView.subviews[i];
circleView.frame = CGRectMake(x,
self.circleViewFrame.origin.y,
self.circleViewFrame.size.width,
self.circleViewFrame.size.height);
x += self.circleViewFrame.size.width + self.delta;
}
}
This is the most important method which will automatically realign all your subviews each time a new circleView is added. The result will look like this:
Simple steps: append circle to container view, resize container view, center align container view
-(void)addToContanerView:(CircleView*)circle{
circle.rect.frame = CGrectMake(containers_end,container_y,no_change,no_change);
[containerView addSubview:circle];
[containerView sizeToFit];
containerView.center = self.view.center;
}
Assumptions:
containers_end & containers_y you can get from CGRectMax function,
for UIView SizeToFit method check here
To take care of rotation use make sure your Autoresizing subviews are set for left, right bottom and top margin.
You can try using this library. I have used it on several of my projects and so far, it worked really well.
https://github.com/davamale/DMHorizontalView

How to get frame of subview after apply transform on mainView?

I have created mainView objcet of UIView and added one subview on it. I applied transform on mainView for reducing frame size. But frame of subview of mainView was not reduced. How to reduce the size of this subview.
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
CGFloat widthM=1200.0;
CGFloat heightM=1800.0;
UIView *mainView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, widthM, heightM)];
mainView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"te.png"]];
[self.view addSubview:mainView];
CGFloat yourDesiredWidth = 250.0;
CGFloat yourDesiredHeight = yourDesiredWidth *heightM/widthM;
CGAffineTransform scalingTransform;
scalingTransform = CGAffineTransformMakeScale(yourDesiredWidth/mainView.frame.size.width, yourDesiredHeight/mainView.frame.size.height);
mainView.transform = scalingTransform;
mainView.center = self.view.center;
NSLog(#"mainView:%#",mainView);
UIView *subMainView= [[UIView alloc] initWithFrame:CGRectMake(100, 100, 1000, 1200)];
subMainView.backgroundColor = [UIColor redColor];
[mainView addSubview:subMainView];
NSLog(#"subMainView:%#",subMainView);
}
NSlog of these views:
mainView:<UIView: 0x8878490; frame = (35 62.5; 250 375); transform = [0.208333, 0, 0, 0.208333, 0, 0]; layer = <CALayer: 0x8879140>>
subMainView:<UIView: 0x887b8c0; frame = (100 100; 1000 1200); layer = <CALayer: 0x887c160>>
Here the width of mainView is 250, the width of subview is 1000. but when i get the output in simulator, subview is occupied correctly, but it's not cross the mainView. How it is possible? How to get frame of subview with respect mainView frame after transformation?
What you're seeing is expected behavior. The frame of an UIView is relative to its parent, so it doesn't change when you apply a transformation to its superview. While the view will appear 'distorted' too, the frame won't reflect the changes since it's still at exact the same position relative to its parent.
However, I assume you would like to get the frame of the view relative to the topmost UIView. In that case UIKit offers these functions:
– [UIView convertPoint:toView:]
– [UIView convertPoint:fromView:]
– [UIView convertRect:toView:]
– [UIView convertRect:fromView:]
I applied these to your example:
CGRect frame = [[self view] convertRect:[subMainView frame] fromView:mainView];
NSLog(#"subMainView:%#", NSStringFromCGRect(frame));
And this is the output:
subMainView:{{55.8333, 83.3333}, {208.333, 250}}
In addition to s1m0n answer, the beautiful thing about applying a transform matrix to your view, is that you can keep reasoning in terms of its original coordinate system (in your case, you can handle subMainView using the non-transformed coordinate system, which is why, even though subMainView's frame is bigger than mainView's transformed frame, it still doesn't cross the parent view, as it gets automatically transformed). This means that when you have a transformed parent view (for example rotated and scaled) and you want to add a subview in a particular point relative to this parent view, you don't have to first keep track of the previous transformations in order to do so.
If you really are interested in knowing the subview's frame in terms of the transformed coordinate system, it will be enough to apply the same transformation to the subview's rectangle with:
CGRect transformedFrame = CGRectApplyAffineTransform(subMainView.frame, mainView.transform);
If you then NSLog this CGRect, you will obtain:
Transformed frame: {{20.8333, 20.8333}, {208.333, 250}}
Which, I believe, are the values that you were looking for. I hope this answers your question!

Start new CAAnimation from current CAAnimation's state

I'm using a pair of CALayers as image masks, allowing me to animate a bulb filling or emptying at a set speed while also following the current touch position. That is, one mask jumps to follow the touch and the other slides to that position. Since I use an explicit animation I'm forced to set the position of the mask sliding mask when I add the animation. This means that if I start a fill and then start an empty before the fill completes the empty will begin from the completed fill position (the opposite is also true).
Is there a way to get the position of the animation, set the position at each step of the animation, or to have the new animation begin from the current state of the active animation?
The code handling the animating is below:
- (void)masksFillTo:(CGFloat)height {
// Clamp the height we fill to inside the bulb. Remember Y gets bigger going down.
height = MIN(MAX(BULB_TOP, height), BULB_BOTTOM);
// We can find the target Y location by subtracting the Y value for the top of the
// bulb from the height.
CGFloat targetY = height - BULB_TOP;
// Find the bottom of the transparent mask to determine where the solid fill
// is sitting. Then find how far that fill needs to move.
// TODO: This works with the new set position, so overriding old anime doesn't work
CGFloat bottom = transMask.frame.origin.y + transMask.frame.size.height;
// If the target is above the bottom of the solid, we want to fill up.
// This means the empty mask jumps and the transparent mask slides.
CALayer *jumper;
CALayer *slider;
if (bottom - targetY >= 0) {
jumper = emptyMask;
slider = transMask;
// We need to reset the bottom to the emptyMask
bottom = emptyMask.frame.origin.y + emptyMask.frame.size.height;
} else {
jumper = transMask;
slider = emptyMask;
}
[jumper removeAllAnimations];
[slider removeAllAnimations];
CGFloat dy = bottom - targetY;
[CATransaction begin];
[CATransaction setValue:(id)kCFBooleanTrue forKey:kCATransactionDisableActions];
[jumper setPosition:CGPointMake(jumper.position.x, jumper.position.y - dy)];
[self slideMaskFillTo:height withMask:slider]; // Do this inside here or an odd flash glitch appears.
[CATransaction commit];
}
// TODO: Always starts from new position, even if animation hasn't reached it.
- (void)slideMaskFillTo:(CGFloat)height withMask:(CALayer *)slider {
// We can find the target Y location by subtracting the Y value for the top of the
// bulb from the height.
CGFloat targetY = height - BULB_TOP;
// We then find the bottom of the mask.
CGFloat bottom = slider.frame.origin.y + slider.frame.size.height;
CGFloat dy = bottom - targetY;
// Do the animation. Animating with duration doesn't appear to work properly.
// Apparently "When modifying layer properties from threads that don’t have a runloop,
// you must use explicit transactions."
CABasicAnimation *a = [CABasicAnimation animationWithKeyPath:#"position"];
a.duration = (dy > 0 ? dy : -dy) / PXL_PER_SEC; // Should be 2 seconds for a full fill
a.fromValue = [NSValue valueWithCGPoint:slider.position];
CGPoint newPosition = slider.position;
newPosition.y -= dy;
a.toValue = [NSValue valueWithCGPoint:newPosition];
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[slider addAnimation:a forKey:#"colorize"];
// Update the actual position
slider.position = newPosition;
}
And an example of how this is called. Notice this means it can be called mid-animation.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self.view];
[self masksFillTo:point.y];
}
If anyone finds it relevant, this is the creation of the images and masks.
// Instantiate the different bulb images - empty, transparent yellow, and solid yellow. This
// includes setting the frame sizes. This approach found at http://stackoverflow.com/a/11218097/264775
emptyBulb = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Light.png"]];
transBulb = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Light-moving.png"]];
solidBulb = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Light-on.png"]];
[emptyBulb setFrame:CGRectMake(10, BULB_TOP, 300, BULB_HEIGHT)]; // 298 x 280
[transBulb setFrame:CGRectMake(10, BULB_TOP, 300, BULB_HEIGHT)]; // 298 x 280
[solidBulb setFrame:CGRectMake(10, BULB_TOP, 300, BULB_HEIGHT)]; // 298 x 280
[self.view addSubview:solidBulb]; // Empty on top, then trans, then solid.
[self.view addSubview:transBulb];
[self.view addSubview:emptyBulb];
// Create a mask for the empty layer so it will cover the other layers.
emptyMask = [CALayer layer];
[emptyMask setContentsScale:emptyBulb.layer.contentsScale]; // handle retina scaling
[emptyMask setFrame:emptyBulb.layer.bounds];
[emptyMask setBackgroundColor:[UIColor blackColor].CGColor];
emptyBulb.layer.mask = emptyMask;
// Also create a mask for the transparent image.
transMask = [CALayer layer];
[transMask setContentsScale:transBulb.layer.contentsScale]; // handle retina scaling
[transMask setFrame:transBulb.layer.bounds];
[transMask setBackgroundColor:[UIColor blackColor].CGColor];
transBulb.layer.mask = transMask;
Was just led to a solution via this answer. If one looks in the right part of the docs, you'll find the following:
- (id)presentationLayer
Returns a copy of the layer containing all properties as they were at the start of the current transaction, with any active animations applied.
So if I add this code before I first check the transparent mask location (aka solid level), I grab the current animated position and can switch between fill up and fill down.
CALayer *temp;
if (transMask.animationKeys.count > 0) {
temp = transMask.presentationLayer;
transMask.position = temp.position;
}
if (emptyMask.animationKeys.count > 0) {
temp = emptyMask.presentationLayer;
emptyMask.position = temp.position;
}
layer.presentation() makes copy of original layer each time you get it, and you need to override init(layer: Any) for every custom CALayer you have.
Another possible way to start from current state is to set animation's beginTime using current animation's beginTime. It allows to start new animation with offset.
In your case it could be (Swift):
if let currentAnimation = slider.animation(forKey: "colorize") {
let currentTime = CACurrentMediaTime()
let animationElapsedTime = currentAnimation.beginTime + currentAnimation.duration - currentTime
a.beginTime = currentTime - animationElapsedTime
}
However this works great for linear timings, but for others it could be difficult to calculate the proper time.

Resources