UIScrollView Custom View rotation problem - objective-c

I have a ScrollView with a Custom View. Now i have the problem with the rotation, the view has after the rotation not the correct frame pos / size.
How can i call the CustomView after rotation for a reposition and resize the frame and content?!
- (void)setupPage
{
NSUInteger nimages = 0;
CGFloat cx = 0;
for (; ; nimages++) {
if (nimages == list.count) {
break;
}
CustomStepView *stepView = [[CustomStepView alloc] initWithFrame:CGRectZero];
stepView.tag = nimages;
if([[UIDevice currentDevice] orientation] == UIDeviceOrientationPortrait || [[UIDevice currentDevice] orientation] == UIDeviceOrientationPortraitUpsideDown)
{
stepView.frame = CGRectMake(cx, 0.0 , 768.0f, 926.0f);
cx += 768.0;
}
else
{
stepView.frame = CGRectMake(cx, 0.0 , 1024.0f, 670.0f);
cx += 1024.0;
}
[scrollView addSubview:stepView];
[stepView release];
}
self.pageControl.numberOfPages = nimages;
}

From what it looks like, You have a lot of subviews(Custom Views) in your ScrollView.
First of all never try to detect the orientation using UIDevice , it is not recommended by apple.
For handling rotations you have been provided with the function : "shouldAutorotateToInterfaceOrientation"
Inside this method, You can simple run a loop for detecting the subviews present inside your scrollview and then set the frame and size of each subview.
This might look something like this:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if(interfaceOrientation == UIInterfaceOrientationPortrait)
{
for(UIView *asubview in scrollView.subviews)
{
asubview.frame = "NEW FRAME";
}
}
else
{
asubview.frame = "NEW FRAME";
}
}
Hope it helps you..although i know this has been asked long back :-)
Cheers

Related

objective c: detect touch began/down when touch started from outside of view/image/button

i have something that resembles a key pad (one master view, nine subviews). the touch began/down may start in any of the nine subviews. i detect touch down/began, to know when user drags their finger into other subviews (which views finger is dragged into), and when user lifts their finger up/touch up/stop.
(stack overflow not letting this question be posted. i'll try pasting the code from answer section, see if that works).
// in IB set long press gesture min duration to 0.1
-(IBAction)handleLongPressOnVoices:(UILongPressGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
[self whichVoiceSelected:location];
} else if(gestureRecognizer.state == UIGestureRecognizerStateChanged){
[self whichVoiceSelected:location];
} else if(gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self.avPlayer stop];
}
}
-(void)whichVoiceSelected:(CGPoint)location {
int updatedSelectedVoiceNumber;
if(CGRectContainsPoint(self.voice0.frame, location)){
updatedSelectedVoiceNumber=0;
self.statusLabelOutlet.text = #"...";
}else if(CGRectContainsPoint(self.voice1.frame, location)){
updatedSelectedVoiceNumber=1;
self.statusLabelOutlet.text = #"...";
}else if(CGRectContainsPoint(self.voice2.frame, location)){
updatedSelectedVoiceNumber=2;
self.statusLabelOutlet.text = #"...";
} ...
...
}
i found parts of the answer across many stackoverflow replies. i am pasting my approach to resolving this question (a) to help the next person and (b) as invitation for better solutions.
// in IB set long press gesture min duration to 0.1
-(IBAction)handleLongPressOnVoices:(UILongPressGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
if (gestureRecognizer.state == UIGestureRecognizerStateBegan){
[self whichVoiceSelected:location];
} else if(gestureRecognizer.state == UIGestureRecognizerStateChanged){
[self whichVoiceSelected:location];
} else if(gestureRecognizer.state == UIGestureRecognizerStateEnded){
[self.avPlayer stop];
}
}
-(void)whichVoiceSelected:(CGPoint)location {
int updatedSelectedVoiceNumber;
if(CGRectContainsPoint(self.voice0.frame, location)){
updatedSelectedVoiceNumber=0;
self.statusLabelOutlet.text = #"...";
}else if(CGRectContainsPoint(self.voice1.frame, location)){
updatedSelectedVoiceNumber=1;
self.statusLabelOutlet.text = #"...";
}else if(CGRectContainsPoint(self.voice2.frame, location)){
updatedSelectedVoiceNumber=2;
self.statusLabelOutlet.text = #"...";
} ...
...
}

Autoscroll UIScrollView when subview is out of the visible area

EDIT:
I uploaded my implementation to github. Maybe it is better to understand what I want and what my problem is.
I changed the code in the github project a little bit than the posted code here. I think the implementation in the github project is better, but not perfect.
What I want to do:
Have a UIScrollView with movable UIViews (e.g. Images). The user can pan this subviews and zoom in and out. When the user zooms in and moves a subview over the current visible area the scrollView should automatically scroll. As lang as the subview is over the edge the scrollview should scroll. When the subview isn't over the visible area anymore the scrollview should stop moving.
I try to explain my problem as good as possible.
What I have managed to do:
Zoom the scrollview, move the subviews with the UIPanGestureRecognizer, recognize when the subview is over the visible area and start moving (changing the contentOffset) the scrollview. Here I using a NSTimer to move the scrollview as long as the subview is over the visible area.
My problem:
When the subview is over the visible area a NSTimer is started, to change the contentOffset of the subview and the frame (position) of the subview.
After that I can't pan the subview anymore.
I can't figure out how to implement the pan gesture with changing the subview frame in a correct way.
My implementation:
I am using three views:
UIScrollView
MyImageContainerView (UIView, added as a subview to the scrollview)
MyImageView (UIView, added as a subview to MyImageContainerView)
Currently MyImageContainerView manages the workflow. A MyImageView has a UIPanGestureRecognizer attached. The method for this recognizer is implemented in MyImageContainerView:
- (void)handlePanGesture:(UIPanGestureRecognizer *)gestureRecognizer
{
//UIView which is moved by the user
MyImageView *currentView = gestureRecognizer.view;
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStatePossible:
{
break;
}
case UIGestureRecognizerStateBegan:
{
//save both values in global instance variables
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//global BOOL variable, to check if scrollView movement is performed
scrolling = NO;
break;
}
case UIGestureRecognizerStateChanged:
{
CGRect rect = CGRectMake(currentFrameOriginX + [gestureRecognizer translationInView:currentView.superview].x, currentFrameOriginY + [gestureRecognizer translationInView:currentView.superview].y, currentView.frame.size.width, currentView.frame.size.height);
if (CGRectContainsRect(currentView.superview.frame, rect)) {
/*PROBLEM: Here is a problem. I need this change of the frame here, to move the UIView along the movement from the user. In my autoScroll-method I have to set the frame of currentView, too. But I can't set the frame of currentView here and in the autoScroll. But as long as the NSTimer runs and is calling autoScroll: this if-statement isn't called, so I can't move the UIView with my finger anymore. */
if (!scrolling) {
//currently the NSTimer method for the automatically scrolling isn't performed, so:
//change the frame according to the pan gesture
currentView.frame = rect;
}
UIScrollView *scrollView = self.myScrollView; //reference to the "root" UIScrollView
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGRect frame = currentView.frame;
CGFloat scale = 1.0 / scrollView.zoomScale;
visibleRect.origin.x *= scale;
visibleRect.origin.y *= scale;
visibleRect.size.width *= scale;
visibleRect.size.height *= scale;
CGSize scrollZone = CGSizeMake(10.0f, 10.0f);
float scrollStep = 3.0f;
CGPoint scrollAmount = CGPointZero;
//determine the change of x and y
if (frame.origin.x+scrollZone.width < visibleRect.origin.x) {
scrollAmount.x = -scrollStep;
}
else if((frame.origin.x+frame.size.width)-scrollZone.width > visibleRect.origin.x + visibleRect.size.width) {
scrollAmount.x = scrollStep;
}
else if (frame.origin.y+scrollZone.height < visibleRect.origin.y) {
scrollAmount.y = -scrollStep;
}
else if((frame.origin.y+frame.size.height)-scrollZone.height > visibleRect.origin.y + visibleRect.size.height) {
scrollAmount.y = scrollStep;
}
if ((scrollAmount.x != 0) | (scrollAmount.y != 0)) {
if (![scrollTimer isValid]) {
//scrollTimer is a global NSTimer instance variable
[scrollTimer invalidate];
scrollTimer = nil;
NSString *scrollString = NSStringFromCGPoint(scrollAmount);
NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:scrollString, #"scrollString", currentView, #"currentView", nil];
scrollTimer = [[NSTimer alloc]initWithFireDate:[NSDate date] interval:0.03f target:self selector:#selector(autoScroll:) userInfo:info repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:scrollTimer forMode:NSRunLoopCommonModes];
}
}
else {
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
}
}
break;
}
case UIGestureRecognizerStateEnded:
{
//quite know the scrolling should stop, maybe it would be better when the scrollView scrolls even if the user does nothing when the subview is over the visible area
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
default:
{
[scrollTimer invalidate];
scrollTimer = nil;
scrolling = NO;
break;
}
}
}
-(void)autoScroll:(NSTimer*)timer {
scrolling = YES; //the scroll method is executed quite know
NSDictionary *info = [timer userInfo];
UIScrollView *scrollView = self.myScrollView;
CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;
CGPoint scrollAmount = CGPointFromString([info objectForKey:#"scrollString"]);
MyImageView *currentView = [info objectForKey:#"currentView"];
//stop scrolling when the UIView is at the edge of the containerView (referenced over 'self')
if ((currentView.frame.origin.x <= 0 | currentView.frame.origin.y <= 0) ||
((currentView.frame.origin.x+currentView.frame.size.width) > self.frame.size.width | (currentView.frame.origin.y+currentView.frame.size.height) > self.frame.size.height)
) {
scrolling = NO;
return;
}
//move the UIView
CGFloat scale = 1.0 / scrollView.zoomScale;
if (scrollAmount.x != 0) {
scrollAmount.x *= scale;
}
if (scrollAmount.y != 0) {
scrollAmount.y *= scale;
}
CGRect frame = currentView.frame;
frame.origin.x += scrollAmount.x;
frame.origin.y += scrollAmount.y;
currentView.frame = frame;
currentFrameOriginX = currentView.frame.origin.x;
currentFrameOriginY = currentView.frame.origin.y;
//move the scrollView
CGPoint contentOffset = scrollView.contentOffset;
contentOffset.x += scrollAmount.x;
contentOffset.y += scrollAmount.y;
[scrollView setContentOffset:contentOffset animated:NO];
}

how to identify the orientation of viewController's subclass in ipad

I have one viewController class example FirstView and it has one subclass ex. secondView.
SecondView is a subclass of FirstView.Now I want to check the orientation in second view?
How to identify the secondView orientation?I checked the following method but its not working.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
When I change the orientation no method is called.
Is there any way to identify the orientation of the custom subclass?
How can I solve this issue?
[[UIApplication sharedApplication] statusBarOrientation] returns you the current device orientation.
I think you can use UIAccelerometer. You just need to implement the UIAccelerometerDelegate.
Just to get the current orientation of the view using Accelerometer you can use this code:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
// Get the current device angle
float x = [acceleration x];
float y = [acceleration y];
float angle = atan2(y, x);
if([acceleration z]>-0.84)
{
if(angle >= -2.25 && angle <= -1) // Current Orientation = Portrait
{
//Do the work
}
else if(angle >= -0.35 && angle < 0.20) //Current Orientation = Landscape Left
{
//Do the work
}
else if(angle >= 0.90 && angle <= 2.00) //Current Orientation = Portrait Upside Down
{
//Do the work
}
else if(angle <= -2.70 || angle >= 2.5) //Current Orientation = Landscape Right
{
//Do the work
}
}
}
Let me know if you need more help on this.
Hope this helps.
In you super class you should call the orientation method of its super like:
Class A {
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[super shouldAutorotateToInterfaceOrientation:interfaceOrientation];
}
}
class b inherits class a {
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
NSLog(#"Your stufs");
}
}

UIView hitTest:withEvent: and pointInside:withEvent

I'm working with Mixare AR SDK for iOS and I need to solve some bugs that happends, one of them is show the information of a POI when the POI's view is tapped.
Prelude:
Mixare has an overlay UIView within MarkerView views are placed, MarkerView views are moving around the screen to geolocate the POIs and each one has two subviews, an UIImageView and an UILabel.
Issue:
Now, for example, there are 3 visible POIs in the screen, so there are 3 MarkerView as overlay subviews. If you touch anywhere in the overlay, a info view associated to a random POI of which are visible is showed.
Desired:
I want that the associated POI's info is shown only when the user tapped a MarkerView
Let's work. I've see that MarkerView inherits from UIView and implements hitTest:withEvent
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
viewTouched = (MarkerView*)[super hitTest:point withEvent:event];
return self;
}
I've put a breakpoint and hitTest is called once for each visible MarkerView but loadedView always is null so I can't work with it, so I've tried to check if the hit point is inside the MarkerView frame implementing pointInside:withEvent: by this way
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
NSLog(#"ClassName: %#", [[self class] description]);
NSLog(#"Point Inside: %f, %f", point.x, point.y);
NSLog(#"Frame x: %f y: %f widht:%f height:%f", self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height);
if (CGRectContainsPoint(self.frame, point))
return YES;
else
return NO;
return YES;
}
But this function always returns NO, even when I touch the MarkerView. When I check the log I saw that X and Y point values has negative values sometimes and width and height of the view are very small, 0.00022 or similar instead of 100 x 150 that I set the MarkerView frame on its initialization.
Here you are a extract of my log in which you can see the class name, the point and the MarkerView frame values.
ClassName: MarkerView
2011-12-29 13:20:32.679 paisromanico[2996:707] Point Inside: 105.224899, 49.049023
2011-12-29 13:20:32.683 paisromanico[2996:707] Frame x: 187.568573 y: 245.735138 widht:0.021862 height:0.016427
I'm very lost with this issue so any help will be welcome. Thanks in advance for any help provided and I'm sorry about this brick :(
Edit:
At last I've found that the problem is not in hitTest:withEvent: or pointInside:withEvent, problem is with CGTransform that applies to the MarkerView for scaling based on distande and rotating the view, if I comment any code related to this, the Mixare AR SDK works fine, I mean, info view is shown correctly if you touch a marker and doesn't do anything if any other place in the screen is touched.
So, by the moment, I've not solved the problem but I applied a patch removing the CGTransform related code in AugmentedViewController.m class - (void)updateLocations:(NSTimer *)timer function
- (void)updateLocations:(NSTimer *)timer {
//update locations!
if (!ar_coordinateViews || ar_coordinateViews.count == 0) {
return;
}
int index = 0;
NSMutableArray * radarPointValues= [[NSMutableArray alloc]initWithCapacity:[ar_coordinates count]];
for (PoiItem *item in ar_coordinates) {
MarkerView *viewToDraw = [ar_coordinateViews objectAtIndex:index];
viewToDraw.tag = index;
if ([self viewportContainsCoordinate:item]) {
CGPoint loc = [self pointInView:ar_overlayView forCoordinate:item];
CGFloat scaleFactor = 1.5;
if (self.scaleViewsBasedOnDistance) {
scaleFactor = 1.0 - self.minimumScaleFactor * (item.radialDistance / self.maximumScaleDistance);
}
float width = viewToDraw.bounds.size.width ;//* scaleFactor;
float height = viewToDraw.bounds.size.height; // * scaleFactor;
viewToDraw.frame = CGRectMake(loc.x - width / 2.0, loc.y-height / 2.0, width, height);
/*
CATransform3D transform = CATransform3DIdentity;
//set the scale if it needs it.
if (self.scaleViewsBasedOnDistance) {
//scale the perspective transform if we have one.
transform = CATransform3DScale(transform, scaleFactor, scaleFactor, scaleFactor);
}
if (self.rotateViewsBasedOnPerspective) {
transform.m34 = 1.0 / 300.0;
double itemAzimuth = item.azimuth;
double centerAzimuth = self.centerCoordinate.azimuth;
if (itemAzimuth - centerAzimuth > M_PI) centerAzimuth += 2*M_PI;
if (itemAzimuth - centerAzimuth < -M_PI) itemAzimuth += 2*M_PI;
double angleDifference = itemAzimuth - centerAzimuth;
transform = CATransform3DRotate(transform, self.maximumRotationAngle * angleDifference / (VIEWPORT_HEIGHT_RADIANS / 2.0) , 0, 1, 0);
}
viewToDraw.layer.transform = transform;
*/
//if we don't have a superview, set it up.
if (!(viewToDraw.superview)) {
[ar_overlayView addSubview:viewToDraw];
[ar_overlayView sendSubviewToBack:viewToDraw];
}
} else {
[viewToDraw removeFromSuperview];
viewToDraw.transform = CGAffineTransformIdentity;
}
[radarPointValues addObject:item];
index++;
}
float radius = [[[NSUserDefaults standardUserDefaults] objectForKey:#"radius"] floatValue];
if(radius <= 0 || radius > 100){
radius = 5.0;
}
radarView.pois = radarPointValues;
radarView.radius = radius;
[radarView setNeedsDisplay];
[radarPointValues release];
}
Any CoreGrapics or UI expert could give us his point of view about this issue??
You should either try to hittest as attached:
if ([self pointInside:point withEvent:event]) {
// do something
}
I would suggest you add the hit test on the superview, and do the following in the hit test of the parent of the markerViews
if ([markerView pointInside:point withEvent:event]) {
// extract the tag and show the relevant info
}
Hope this helps

Manual Rotation Method Breaks with Side Switch

Currently I'm workign on a drawing app for the iPad. I need to reposition and rotate the toolbar in the app when it is put into a different orientation while keeping the the drawing area in the same place.
I found a method here for doing this. It uses the NSNotificationCenter to monitor for rotation changes. This calls a custom didRotate: method that will rotate and reposition my toolbar based on the UIDeviceOrientation.
This part works fine. However, whenever the side switch on the iPad is engaged to lock the orientation, the toolbar repositions to the location is was at launch.
For example: If I start the application in landscape left and rotate it to portrait, the toolbar will reposition to the bottom of the screen. However as soon as I engage the slide switch, it moves to the side of the screen for the landscape left orientation.
The methods I'm using for this are all below.
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
- (void)didRotate:(NSNotification *)notification {
UIDeviceOrientation deviceOrientation = [[UIDevice currentDevice] orientation];
UIInterfaceOrientation interfaceOrientation;
bool orientationFound = YES;
if (deviceOrientation == UIDeviceOrientationPortrait) {
interfaceOrientation = UIInterfaceOrientationPortrait;
} else if (deviceOrientation == UIDeviceOrientationPortraitUpsideDown) {
interfaceOrientation = UIInterfaceOrientationPortraitUpsideDown;
} else if (deviceOrientation == UIDeviceOrientationLandscapeLeft) {
interfaceOrientation = UIInterfaceOrientationLandscapeRight;
} else if (deviceOrientation == UIDeviceOrientationLandscapeRight) {
interfaceOrientation = UIInterfaceOrientationLandscapeLeft;
} else {
orientationFound = NO;
}
if (orientationFound) {
[self.toolbar changeToOrientation:interfaceOrientation withDuration:.25];
[self.tutorialOverlay changeToOrientation:interfaceOrientation];
}
}
- (void)changeToOrientation:(UIInterfaceOrientation)orientation withDuration:(float)duration {
float angle;
CGPoint origin;
if (orientation == UIInterfaceOrientationPortrait) {
angle = portraitAngle;
origin = self.portraitOrigin;
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
angle = portraitUpsideDownAngle;
origin = self.portraitUpsideDownOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeLeft) {
angle = landscapeLeftAngle;
origin = self.landscapeLeftOrigin;
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
angle = landscapeRightAngle;
origin = self.landscapeRightOrigin;
}
[UIView animateWithDuration:duration animations:^{
self.transform = CGAffineTransformMakeRotation(angle);
CGRect rect = self.frame;
rect.origin = origin;
self.frame = rect;
}];
}
I'd strongly recommend against using this notification-based approach. Note that the name of the notification is UIDeviceOrientationDidChangeNotification; the device orientation is not the same as the interface orientation, and it leads to lots of little issues like this. (The device orientation, for example, includes UIDeviceOrientationFaceDown, which is never associated with an interface orientation.)
I'd suggest letting your view controller automatically rotate itself; this will place the toolbar, etc, for you. You can then override - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration to put the drawing area back to the orientation you want to maintain. (You'd use similar code to your changeToOrientation method above, but for the drawing area instead, and you don't need to create your own animation block. Also, the angles would all be negated, because you're undoing the change the view controller made.)
I just answered a similar question here.
Basically just allow the interface to rotate, but rotate the view you don't want to rotate 'back' in willRotateToInterfaceOrientation:duration:.