I've been reading through theses pages for a while now and have been having some trouble getting my CGRect to load properly. I learned it was because I'm forcing an orientation after viewDidLoad runs, and I'm trying to get the CGRect to draw again after. Basically, this is what I'm looking at:
-(void)viewDidLoad
{
//mybutton.frame = CGRectMake(...);
//...
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return UIInterfaceOrientationIsLandscape(interfaceOrientation);
}
When the view appears, the CGRect is very squashed and distorted, due to the rotation of the view.
You could extract the drawing code out from viewDidLoad and into its own function. Then you can call the function from within the shouldAutorotateToInterfaceOrientation and from the viewDidLoad.
Related
Using iOS8. Frame size of UIViewController's view is incorrect in viewDidLoad and viewDidAppear.
I could get the correct frame size from viewDidLayoutSubviews and perform view updates (I need to invalidate a layout) but than handling rotation with viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator becomes pointless and the 2 methods duke it out.
Ideally willTransitionToSize would be called and not didLayoutSubviews, but not both, because I don't know if I truly need to invalidate the layout and perform pointless calculation, layout, etc.
I want to avoid using all sorts of magic flags pertaining to rotation and frame size changes.
Is there any way to get the "true" size of the view controller view before any of this happens?
EDIT
Here is some example code:
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(#"My Frame = %#",NSStringFromCGRect(self.view.frame)); // prints 320x568 i.e. the whole screen
// more setup code...
self.someView = [SomeClass alloc] initWithFrame:self.view.bounds];
self.someView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
// add some more views and masks
}
-(void)viewDidAppear {
[super viewDidAppear];
NSLog(#"My Frame = %#",NSStringFromCGRect(self.view.frame)); // prints 320x568 i.e. the whole screen
// view "sticks" out the bottom of the screen still - don't know if I need to layout "self.someView". I could add "viewWillLayoutSubviews" but than rotation wouldn't be animated - because I wouldn't know which condition to take...
}
// if the view controller rotates than need to update appearance in a nice animated way
-(void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[self updateAppearance];
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
}];
}
I'm using the iCarousel library to show a coverflow-like UI. Animating the iCarousel subviews directly tends to blow up, so I create a wrapper for my animation and stick a subview inside of it which looks like the tapped cover. Then I hide the actual iCarousel view, and animate the fake cover on top of it.
I'm using UIView's transitionWithView:duration:options:animations:completion: method, but I'm running into some trouble. When animating another view onscreen for the first time, the view appears without any animation. When animating out, the view correctly flips and hides.
My view hierarchy is as follows:
main view, loaded from a nib
wrapper view
view to transition from
view to transition to
The view I'm transitioning to is a UINavigationController's view which contains a UITableViewController subclass. Instead of the initial animation, the UINavigationController appears and then the view grows upward a little, as if it's taking over the space otherwise occupied by a status bar.
Any idea why the table view might be animating like this? (I suspect containment APIs and/or wantsFullscreen, although I'm not explicitly using them. I simply install the views into the wrapper via addSubview:.)
Here's my "flip in" code, that animates every time but the first:
- (void) flipInWithCompletion:(MBTransitionCompletion)completion {
BOOL displayingPrimary = [self isDisplayingPrimaryView];
UIView *frontView = [self frontView];
UIView *backView = [self backView];
UIView *wrapperView = [self wrapperView];
[wrapperView addSubview:frontView];
[UIView transitionWithView:wrapperView
duration:0.8
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
[wrapperView addSubview:backView];
[frontView removeFromSuperview];
}
completion:^(BOOL finished) {
[self setIsDisplayingPrimaryView:!displayingPrimary];
if (completion) {
completion();
}
}
];
}
What might cause the table view to grow instead of allowing the wrapper to flip?
Edit:
I've made a video demoing the exact problem.
Sounds like the view isn't properly loaded before it starts to animate in. I think I remember having a similar problem before. Try adding it to your view first, then remove it, and try to animate it into place to see if that fixes it.
That's not really a solution though, but it should give you a clue as to what might be wrong.
I have a custom map of a limited area, and have it set up to correctly show the users' location. The map is a 1600px square image within a UIScrollView.
I have a crosshair image to show the current location of the user, which at zoomScale 1.0 is the desired size. When I pinch and zoom the scrollView, the crosshair scales with it. I would like to have the subview remain the same size on screen.
I haven't been able to find any information on this, what would be the best way to go about this?
If there is anything I can provide you with to help the answer, please let me know.
Many thanks!
EDIT -
Having looked in to this further, there is a UIScrollViewDelegate method - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale which I tried using to take the marker's current center and size, then adjust, but this only scales at the end of the zoom. I would prefer to have the marker remain the same size while the user is zooming.
EDIT 2-
Cake has provided a great answer below, but I haven't been able to implement this in the way I imagined it would be.
I have the UIImageView as a placeholder, with alpha set to 0. This placeholder moves around relative to the map to show the user location. This operates as I expect it to. Unfortunately, this resizes with the map, as it is a subview of the map (so it stays in place).
Taking Cake's below answer, I have created the non-scaling crosshair image, and added it as a sibling subview to the scrollview. The maths, once Cake had pointed them out, were quite simple to get the new frame for the crosshair:
CGPoint ULPC = userLocationPlaceholder.center;
float zs = scrollView.zoomScale;
CGRect newFrame = CGRectMake(((ULPC.x * zs) - scrollView.contentOffset.x) - 20, ((ULPC.y * zs) - scrollView.contentOffset.y) - 20, 40, 40);
Where the image is 40points wide. This matches the centers perfectly.
The problem I now have is that I cannot get the crosshair image to stay locked to the placeholder.
I have tried using a self calling animation as such:
-(void)animeUserLocationAttachment
{
[UIView animateWithDuration:0.05
delay:0
options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionCurveLinear )
animations:^{
userLocationDotContainer.frame = newFrame;
} completion:^(BOOL finished){
// Call self
[self animateUserLocationAttachment];
}];
}
As soon as I start scrolling/zooming, this locks the animation so that the crosshair just sits in place until I release the scrolling/zooming, then it correctly updates it's location.
Is there any way I can get around this, or an alternative method I can apply?
Many thanks
EDIT 3 -
I've re-accepted Cake's answer as it covers 90% of the issue. Further to his answer I have implemented the ScrollViewDelegate methods scrollViewWillBeginDragging: andscrollViewWillBeginDecelerating: to scale the placeholder to match the current size of the crosshair relative to the map, show the placeholder (that is a subview of the map image) and hide the crosshair image. The delegate method scrollviewWillBeginZooming:withView: does not show the placeholder because it scales with the map. As Cake recommends, I'll make a new question for this issue.
The counterpart methods (scrollViewDidEndZooming:withView:atScale:, scrollViewDidEndDragging:willDecelerate: and -scrollViewDidEndDecelerating:`) all hide the placeholder, and re-show the crosshair.
The question is old but for the future similar questions I've recently resolved a similar problem applying the hint of Andrew Madsen of another post.
I'had a UIScrollView, with an UIImageView in it. Attached to the UIImageView I had many MKAnnotationView (those are my subviews that I didn't want scaling with the superview).
I did subclass UIImageView and implement setTransform: method like here:
#import "SLImageView.h"
#implementation SLImageView
- (void)setTransform:(CGAffineTransform)transform
{
[super setTransform:transform];
CGAffineTransform invertedTransform = CGAffineTransformInvert(transform);
for (id obj in self.subviews)
{
if ([obj isKindOfClass:[MKAnnotationView class]])
{
[((UIView *)obj) setTransform:invertedTransform];
}
}
}
#end
This works perfectly!
Mick.
Create another crosshair image that's associated with the view or view controller that contains the scrollview. Then have this one always snap to the center of the crosshair image you already have. Then, hide your original crosshair image. Then you can avoid having the scrollview scale the disassociated crosshair, and it should stay the same size.
Relative coordinate systems
Each view in cocoa touch has a frame property that has an origin. In order to position an object owned by one view properly relative to another view, all you have to do is figure out the differences in their origins. If one view is a subview of another, then this isn't too difficult.
Get the origin of the container view
Get the location of the subview inside of the container view
Get the origin of the subview
Calculate the difference in the positions of the origins
Get the location of the object you want to overlap (relative to the subview)
Calculate the location of the object you want to overlap relative to the container view
Move your crosshair to this position
Swift equivalent for Mick's answer:
class MapContainerView:UIView {
#IBOutlet var nonScalingViews: [UIView]!
override var transform: CGAffineTransform {
didSet {
guard let nonScalingViews = nonScalingViews else {
return
}
let invertedTransform = CGAffineTransformInvert(transform)
for view in nonScalingViews {
view.transform = invertedTransform
}
}
}
}
I have a UIViewController subclass that can display animations (mostly changing alphas on imageview subviews). If an animation is occurring when the device is rotated, I would like the rotation animation to occur after the current animation block completes. How can I make this happen?
When you call a method that animated an image, set some BOOLean value to NO and conditionally allow rotation in the -shouldAutorotateToInterfaceOrientation method. Something like this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if (!someBool)
return NO;
return YES;
}
I have a UIViewController one UIWebView in it. I'd like the UIWebView to be positioned in the centre of the iPad screen in landscape and portrait modes. So, I've implemented it like this
// UIViewController
// InfoGraphicView is the UIWebView
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Overriden to allow any orientation.
return YES;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration {
if (toInterfaceOrientation == UIInterfaceOrientationPortrait ||
toInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
[self layoutPortrait];
} else {
[self layoutLandscape];
}
}
- (void)layoutLandscape {
NSLog(#"Layout Landscape");
infoGraphicView.frame = CGRectMake(100, 100, 936, 700);
}
- (void)layoutPortrait {
NSLog(#"Layout Portrait");
infoGraphicView.frame = CGRectMake(100, 100, 700, 936);
}
However, it's not behaving as I expected. In the above code, I would expectt he UIWebView to be 100px (or points or whatever the unit is) away from the top and the left. But it's not. In Portrait mode it appears flush with the top left of the screen, and in Landscape mode it seems to be partially offscreen in the top left.
If I set the frame as CGRectMake(-100, 100, 700, 936) then I get it positioned in the center of the screen as I'd like it to be, but I've no idea why.
As usual, there's most likely something simple I'm overlooking but I can't figure it out. Any help greatly appreciated as always.
The coordinates you set on infoGraphicView are relative to its superview, not to the screen generally. And views don't necessarily clip their subviews. Furthermore, the shape set automatically to self.view will depend on the scaling flags set in Interface Builder. However, I think that by default it is set to fill the whole screen.
That said, I think the mistake is in your use of willRotateToInterfaceOrientation:duration:. That is called before the rotation begins, so self.view has the old size (ie, it'll still be portrait sized if rotating from portrait to landscape and vice versa). Probably better to hook willAnimateRotationToInterfaceOrientation:duration: — then the correct size has been set and you'll be within the CoreAnimation block so your view will grow/shrink as part of the rotation animation.
It's also worth checking which resizing flags you have set on infoGraphicView. They'll take effect automatically, in addition to any changes you make. So you probably want to disable them all.
This probably is an issue with the view that the web view is in. The coordinate system used is that of the view’s superview. If that view isn’t being resized on rotation, then you’ll see unexpected layout like this. You can access the superview of a view through the superview property; one way to see its frame would be to use its description. Put this line in one of your layout methods:
NSLog(#"Superview: %#", [infoGraphicView superview]);
That should print out a description of the view.
Once you get that figured out, if you want the web view to have the same layout, you can use its autoresizingMask property. If you set it like this:
infoGraphicView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Then the view will automatically change its width and height to keep the top, left, right, and bottom margins the same.