Before iOS6 I used to rotate the MKMapView based on the user's GPS-direction (NOT HEADING), using a subview of the MKMapView. It all worked perfectly, I initialized the view as follows:
mapView = [[MKMapView alloc] init];
mapView.delegate = self;
mapView.clipsToBounds = FALSE;
mapView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
self.view = mapView;
And when rotating I did the following:
UIView *innerMap = [mapView.subviews objectAtIndex:0];
if([innerMap isKindOfClass:[UIView class]])
{
innerMap.transform = CGAffineTransformMakeRotation(degreesToRadians(-lastDegrees));
}
As I said, all worked perfectly. The nice thing was then when the map rotated, it automatically scaled to the entire frame of the view and filled the black corners that the rotation created.
However, the new iOS6 Maps does not scale and fill the corners! So when it rotates, half of the view is missing and the other half is black (background-color).
What have I tried?
Trying all different subviews of the new MKMapview (also set their resizingmask to the same value as the mapview)
Tried to set the contentmode to scaleToFill (also in the subviews)
Tried to set the autoresizes subviews to true
Tried to reset the frame after rotating
Unfortunately, all my attempts have no effect at all. Somehow the GoogleMaps scaled and filled the black corners automatically and the iOS Maps do not. So am I missing something here, some awesome property of UIView that I'm forgetting or is this simply not possible with the new iOS6 Maps?
What about making the MapView much larger than the screen size so when it rotates you can't seen around it?
Related
To describe my project:
I have a rectangle UIImageView frame floating over a white layer. Inside the UIImageView, I'm successfully creating the illusion that it is showing a portion of a background image behind the white layer. You can drag the rectangle around, and it will "redraw" the image so that you can peer into what is behind the white. Its basically this code:
//whenever the frame is moved, updated the CGRect frameRect and run this:
self.newCroppedImage = CGImageCreateWithImageInRect([bgImage.image CGImage], frameRect);
frame.image = [UIImage imageWithCGImage:self.newCroppedImage];
Anyhow, I also have a rotation gesture recognizer that allows the user to rotate the frame (and consequentially rotates the image). This is because the CGRect sent to the CGImageCreateWithImageInRect is still oriented at its original rotation. This breaks the illusion that you're looking through a window because the image you see is rotated when only the frame should appear that way.
So ideally, I need to take the rotation of my frame and apply it to the image created from my bgImage. Does anyone have any clues or ideas on how I could apply this?
I suggest you take a different approach. Don't constantly create new images to put in your UIImageView. Instead, set up your view hierarchy like this:
White view
"Hole" view (just a regular UIView)
Image view
That is, the white view has the hole view as a subview. The hole view has the UIImageView as its subview.
The hole view must have its clipsToBounds property set to YES (you can set it with code or in your nib).
The image view should have its size set to the size of its image. This will of course be larger than the size of the hole view.
And this is very very important: the image view's center must be set to the hole view's center.
Here's the code I used in my test project to set things up. The white view is self.view. I start with the hole centered in the white view, and I set the image view's center to the hole view's center.
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:YES];
CGRect bounds = self.view.bounds;
self.holeView.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
self.holeView.clipsToBounds = YES;
bounds = self.holeView.bounds;
self.imageView.center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
self.imageView.bounds = (CGRect){ CGPointZero, self.imageView.image.size };
}
I also set the image view's size to the size of its image. You might want to set it to the size of the white view.
To pan and rotate the hole, I'm going to set holeView.transform. I'm not going to change holeView.frame or holeView.center. I have two instance variables, _holeOffset and _holeRotation, that I use to compute the transform. The trick to making it seem like a hole through the white view, revealing the image view, is to apply the inverse transform to the image view, undoing the effects of the hole view's transform:
- (void)updateTransforms {
CGAffineTransform holeTransform = CGAffineTransformIdentity;
holeTransform = CGAffineTransformTranslate(holeTransform, _holeOffset.x, _holeOffset.y);
holeTransform = CGAffineTransformRotate(holeTransform, _holeRotation);
self.holeView.transform = holeTransform;
self.imageView.transform = CGAffineTransformInvert(holeTransform);
}
This trick of using the inverse transform on the subview only works if the center of the subview is at the center of its superview. (Technically the anchor points have to line up, but by default the anchor point of a view is its center.)
I put a UIPanGestureRecognizer on holeView. I configured it to send panGesture: to my view controller:
- (IBAction)panGesture:(UIPanGestureRecognizer *)sender {
CGPoint offset = [sender translationInView:self.view];
[sender setTranslation:CGPointZero inView:self.view];
_holeOffset.x += offset.x;
_holeOffset.y += offset.y;
[self updateTransforms];
}
I also put a UIRotationGestureRecognizer on holeView. I configured it to send rotationGesture: to my view controller:
- (IBAction)rotationGesture:(UIRotationGestureRecognizer *)sender {
_holeRotation += sender.rotation;
sender.rotation = 0;
[self updateTransforms];
}
This should be a pretty common thing to do, but I haven't been able to get it to work exactly right.
I have rectangular content. It normally fits in 320x361: portrait mode minus status bar minus ad minus tab bar.
I have put that content in a UIScrollView and enabled zooming. I also want interface rotation to work. The content will always be a tall rectangle, but when zoomed users might want to see more width at a time and less height.
What do I need to do in Interface Builder and code to get this done? How should I set my autoresizing on the different views? How do I set my contentSize and contentInsets?
I have tried a ton of different ways and nothing works exactly right. In various of my solutions, I've had problems with after some combination of zooming, interface rotation, and maybe scrolling, it's no longer possible to scroll to the entire content on the screen. Before you can see the edge of the content, the scroll view springs you back.
The way I'm doing it now is about 80% right. That is, out of 10 things it should do, it does 8 of them. The two things it does wrong are:
When zoomed in portrait mode, you can scroll past the edge of the content, and see a black background. That's not too much to complain about. At least you can see all the content. In landscape mode, zoomed or not, seeing the black background past the edge is normal, since the content doesn't have enough width to fill the screen at 1:1 zoom level (the minimum).
I am still getting content stuck off the edge when it runs on a test device running iOS 3.0, but it works on mine running 4.x. -- Actually that was with the previous solution. My tester hasn't tried the latest solution.
Here is the solution I'm currently using. To summarize, I have made the scroll view as wide and tall as it needs to be for either orientation, since I've found resizing it either manually or automatically adds complexity and is fragile.
View hierarchy:
view
scrollView
scrollableArea
content
ad
view is 320x411 and has all the autoresizing options on, so conforms to screen shape
scrollView is 480 x 361, starts at origin -80,0, and locks to top only and disables stretching
scrollableArea is 480 x 361 and locks to left and top. Since scrollView disables stretching, the autoresizing masks for its subviews don't matter, but I tell you anyway.
content is 320x361, starts at origin 80,0, and locks to top
I am setting scrollView.contentSize to 480x361.
shouldAutorotateToInterfaceOrientation supports all orientations except portrait upside down.
In didRotateFromInterfaceOrientation, I am setting a bottom content inset of 160 if the orientation is landscape, resetting to 0 if not. I am setting left and right indicator insets of 80 each if the orientation is portrait, resetting if not.
scrollView.minimumZoomScale = 1.0
scrollView.maximumZoomScale = 2.0
viewForZoomingInScrollView returns scrollableArea
// in IB it would be all options activated
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
scrollView.contentSize = content.frame.size; // or bounds, try both
what do you mean with scrollableArea?
your minZoomScale is set to 1.0 thats fine for portrait mode but not for landscape. Because in landscape your height is smaller than in portrait you need to have a value smaller than 1.0. For me I use this implementation and call it every time, the frame of the scrollView did change:
- (void)setMaxMinZoomScalesForCurrentBounds {
CGSize boundsSize = self.bounds.size; // self is a UIScrollView here
CGSize contentSize = content.bounds.size;
CGFloat xScale = boundsSize.width / contentSize.width;
CGFloat yScale = boundsSize.height / contentSize.height;
CGFloat minScale = MIN(xScale, yScale);
if (self.zoomScale < minScale) {
[self setZoomScale:minScale animated:NO];
}
if (minScale<self.maximumZoomScale) self.minimumZoomScale = minScale;
//[self setZoomScale:minScale animated:YES];
}
- (void)setFrame:(CGRect)rect { // again, this class is a UIScrollView
[super setFrame:rect];
[self setMaxMinZoomScalesForCurrentBounds];
}
I don't think I understood the entire problem from your post, but here's an answer for what I did understand.
As far as I know (and worked with UIScrollView), the content inside a UIScrollView is not automatically autoresized along with the UIScrollView.
Consider the UIScrollView as a window/portal to another universe where your content is. When autoresizing the UIScrollView, you are only changing the shape/size of the viewing window... not the size of the content in the other universe.
However, if needed you can intercept the rotation event and manually change your content too (with animation so that it looks good).
For a correct autoresize, you should change the contentSize for the scrollView (so that it knows the size of your universe) but also change the size of UIView. I think this is why you were able to scroll and get that black content. Maybe you just updated the contentSize, but now the actuall content views.
Personally, I haven't encountered any case that required to resize the content along with the UIScrollView, but I hope this will get you started in the right direction.
If I understand correctly is that you want a scrollview with an image on it. It needs to be fullscreen to start with and you need to be able to zoom in. On top of that you want it to be able to rotate according to orientation.
Well I've been prototyping with this in the past and if all of the above is correct the following code should work for you.
I left a bit of a white area for the bars/custombars.
- (void)viewDidLoad
{
[super viewDidLoad];
//first inits and allocs
scrollView2 = [[UIScrollView alloc] initWithFrame:self.view.frame];
imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"someImageName"]];
[scrollView2 addSubview:imageView];
[self drawContent]; //refreshing the content
[self.view addSubview:scrollView2];
}
-(void)drawContent
{
//this refreshes the screen to the right sizes and zoomscales.
[scrollView2 setBackgroundColor:[UIColor blackColor]];
[scrollView2 setCanCancelContentTouches:NO];
scrollView2.clipsToBounds = YES;
[scrollView2 setDelegate:self];
scrollView2.indicatorStyle = UIScrollViewIndicatorStyleWhite;
[scrollView2 setContentSize:CGSizeMake(imageView.frame.size.width, imageView.frame.size.height)];
[scrollView2 setScrollEnabled:YES];
float minZoomScale;
float zoomHeight = imageView.frame.size.height / scrollView2.frame.size.height;
float zoomWidth = imageView.frame.size.width / scrollView2.frame.size.width;
if(zoomWidth > zoomHeight)
{
minZoomScale = 1.0 / zoomWidth;
}
else
{
minZoomScale = 1.0 / zoomHeight;
}
[scrollView2 setMinimumZoomScale:minZoomScale];
[scrollView2 setMaximumZoomScale:7.5];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
if (interfaceOrientation == UIInterfaceOrientationPortrait || interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown) {
// Portrait
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.width, self.view.frame.size.height - 88);
[self drawContent];
}
else {
// Landscape
//the 88pxls is the white area that is left for the navbar etc.
self.scrollView2.frame = CGRectMake(0, 88, [UIScreen mainScreen].bounds.size.height, self.view.frame.size.width);
[self drawContent];
}
return YES;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
I hope this will fix your troubles. If not leave a comment.
When you want to put a content (a UIView instance, let's call it theViewInstance ) in a UIScrollView and then scroll / zoom on theViewInstance , the way to do it is :
theViewInstance should be added as the subview of the UIScrollView
set a delegate to the UIScrollView instance and implement the selector to return the view that should be used for zooming / scrolling:
-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return theViewInstance;
}
Set the contentSize of the UIScrollView to the frame of the theViewInstance by default:
scrollView.contentSize=theViewInstance.frame.size;
(Additionally, the accepted zoom levels can be set in the UIScrollView :)
scrollView.minimumZoomScale=1.0;
scrollView.maximumZoomScale=3.0;
This is the way a pinch to zoom is achieved on a UIImage : a UIImageView is added to a UIScrollView and in the UIScrollViewDelegate implementation, the UIImageView is returned (as described here for instance).
For the rotation support, this is done in the UIViewController whose UIView contains the UIScrollView we just talked about.
Very strange behavior, there is a round dot in the center of the screen using this code, and a UIScrollview with nothing inside in a nib. I expect that UIScrollview should be empty. The dot blurs and disappears when I scroll the screen.
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *subviews = [closetScroll subviews];
UIImageView *strange=[subviews objectAtIndex:0];
strange.center = CGPointMake([[UIScreen mainScreen] bounds].size.width/2, [[UIScreen mainScreen] bounds].size.height/2);
strange.alpha=1;
NSLog(#"%#",subviews);
}
The console output is:
<UIImageView: 0x4b1f780; frame = (380.5 508.5; 7 7); opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x4b1f820>
Does anyone know why?
After magnifying the UIImageView and tweaking with configurations, I have come to conclude that the UIImageView is actually the scroll bar, and if horizontal and vertical scroll is enabled, an "empty" UIScrollview has two subviews inside.
I had the same issue. Calculating the number of subviews can be very deceptive, because of this "feature".
Start the application and count the number of subviews, this will be 1. Now, use you mouse in the simulator or finger on the device and swipe from the right to the left. Count the number of subviews. The number will be 2.
I can deduce nothing else than that this extra UIImageView is produced by Cocoa Touch to render the background in the right color when "bouncing" beyond the end of the UIScrollView's bounds.
I am trying to implement an adWhirl view in the same view as one that has an MKMapView object. If I follow the standard steps that work fine with my tableViews, that is
awView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
[self.view addSubview:awView];
then the funny behavior starts. In the simulator it receives the touch and sends you on. But when running on my test device (running 3.1.3) the touch passes through to the map.
I have been told that this is because awView is being cast as a subView of mapView and that it must be its own view. But how? I have tried creating a separate UIView and then placing awView in it, then locating it at a fixed location, but instead of the fixed location, it loads relative to mapView and still does not receive touches.
Any suggestions?
Addendum: I thought that I was adding both as subviews but have not had any success. What I had done is to create two views in IB. The top one (firstView) has two subviews (bannerForAd and mapView.) This is what I have now
self.awView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
self.awView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[bannerForAd addSubview:awView];
mapView = [[MKMapView alloc] initWithFrame:self.view.frame];
mapView = (MKMapView*)self.view;
mapView.delegate = self;
[firstView addSubview:mapView];
[firstView bringSubviewToFront:bannerForAd];
Now the map shows fine, but the ad is not even visible.
Stick both the AdWhirlView and the MKMapView as a subview of a UIView.
I have a bunch of 'rowviews' that I want to put in a vertical scroll view. I have created this rowView view as a separate nib in IB. They are sized at 1024/200 (ipad). Now I want to put them one by one in my parent UIScrollView. I tried a simple [vScroll addSubview:rowView] but this puts them overtop of eachother (I made the rowview transparent to check this). So then I started fooling with the bounds of each rowview to no avail. This is my code. Note 'self.yExtentSoFar' is initialised to 0. Imagine the code below called for each row:
MyRowView *rowView = [[MyRowView alloc] init];
float calculatedWidth = 0;
// minus nav bar
float calculatedHeight = 0;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
UIInterfaceOrientation orientation = [UIDevice currentDevice].orientation;
if (orientation == UIInterfaceOrientationPortrait){
// iPad
calculatedWidth = 768.0;
calculatedHeight = 960.0;
}else{
// iPad
calculatedWidth = 1024.0;
calculatedHeight = 704.0;
}
[self.vScroll addSubview: rowView.view];
[rowView.view setBounds:CGRectMake(0, self.yExtentSoFar, calculatedWidth,200)];
[self.vScroll setContentSize:CGSizeMake(calculatedWidth, yExtentSoFar+200)];
self.yExtentSoFar += 200;
So before I tried settings bounds the rowviews appeared overtop of each other. Understandable I guess. When I set the bounds, the 2nd row view hasn't appeared under the 1st as expected, instead I have to pull down the vScroll and the 2nd has appeared ABOVE the first off screen!
Could someone point to where I'm going wrong? Thanks a lot,
Mike
You want to layout your subviews by setting their frame.
Specifically you're confusing the reference co-ordinates.. bounds refers to how much of that view to show. Whereas the frame is where (& what size) should the view be placed in it's superview.
See "The Relationship of the Frame, Bounds, and Center" View Programming Guide for iPhone
You're doing it wrong ;-) What you have explained here is more or less a re-implementation of what you get using a UITableView. Use a UITableView and a custom table view cell. It will make your life much easier.