Zooming within UIWebView crashes app - ios

I've looked at 20+ different posts that are vaguely related to this and haven't been able to find a solution.
What I have is a class which subclasses UIViewController. On this class's loadView method, I'm adding a UIWebView inside of a UIScrollView. The UIWebView has scalesPageToFit set to YES. The problem I'm facing is that it seems like zooms are being multiplied! i.e. When you zoom inside the web view the very first time, everything seems to work properly. When you zoom AGAIN, it seems that it takes your already zoomed version and tries to amplify that by an additional factor. This causes the app to crash. Any thoughts on how to fix this? I think something about the reported zoomScale from the UIWebView is messing things up.
Here's a modified sample code to get an idea of the current implementation:
# This is a UIViewController
#implementation MyViewController
- (void)loadView
{
[super loadView];
self.view.accessibilityLabel = #"MyView";
self.scrollView = [[UIScrollView alloc] init];
self.scrollView.backgroundColor = [UIColor blueColor];
self.scrollView.scrollsToTop = YES;
self.scrollView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:self.scrollView];
// constraints removed from sample for readability
self.headerView = [[UIViewController alloc] init];
[self.scrollView addSubview:self.headerView];
self.bodyView = [[UIWebView alloc] init];
self.bodyView.delegate = self;
self.bodyView.scalesPageToFit = YES;
self.bodyView.scrollView.scrollsToTop = NO;
self.bodyView.translatesAutoresizingMaskIntoConstraints = NO;
[self.bodyView.scrollView addObserver:self
forKeyPath:KeyValuePath
options:NSKeyValueObservingOptionNew
context:nil];
[self.scrollView addSubview:self.bodyView];
// other constraints here
}
Any help or feedback would be immensely appreciated.

Related

How to have images that pinch-zoom in a paginating scroll view in iOS?

I'm trying to use a scroll view to have pagination with pages of subviews that are images that can be pinched zoomed on iOS. The pagination works, but as soon as an image is pinch-zoomed, the app crashes with EXEC_BAD_ACCESS(code=1,address=...)
I'm aware that it's a bit odd to swipe a zoomed image to pan the image and also swipe to paginate, but in the real app, the pagination will be done with a page control. Also I think it could work like the preview app. If an image is zoomed, panning will go down to the bottom of the image and then after that is reached, it goes to the next image.
Is this possible?
Here's an example:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
ScrollerViewController *viewController = [[ScrollerViewController alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = viewController;
[self.window makeKeyAndVisible];
return YES;
}
ScrollerViewController.m - the outer pagination view controller
- (void)viewDidLoad {
[super viewDidLoad];
// outer scroll view for paging with two pages
CGRect frame = CGRectMake(0,0,self.view.bounds.size.width,self.view.bounds.size.height);
UIScrollView *pagingScroller = [[UIScrollView alloc] initWithFrame:frame];
pagingScroller.pagingEnabled = YES;
pagingScroller.scrollsToTop = NO;
pagingScroller.userInteractionEnabled = YES;
pagingScroller.contentSize = CGSizeMake(self.view.bounds.size.width*2,self.view.bounds.size.height);
// first page
ImageViewController *page1 = [[ImageViewController alloc] init];
page1.filename = #"cat.jpg";
page1.view.frame = CGRectMake(0,0,self.view.bounds.size.width,self.view.bounds.size.height);
[pagingScroller addSubview:page1.view];
// second page
ImageViewController *page2 = [[ImageViewController alloc] init];
page2.filename = #"dog.jpg";
page2.view.frame = CGRectMake(self.view.bounds.size.width,0,self.view.bounds.size.width,self.view.bounds.size.height);
[pagingScroller addSubview:page2.view];
self.view = pagingScroller;
}
ImageViewController.m - the pinch-zoom image
- (void)viewDidLoad {
[super viewDidLoad];
// scroll view for pinch zooming
CGRect frame = CGRectMake(0,0,self.view.bounds.size.width,self.view.bounds.size.height);
UIScrollView *zoomScroller = [[UIScrollView alloc] initWithFrame:frame];
zoomScroller.minimumZoomScale = 1.0;
zoomScroller.maximumZoomScale = 5.0;
zoomScroller.userInteractionEnabled = YES;
zoomScroller.delegate = self;
imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.userInteractionEnabled = YES;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = [UIImage imageNamed:filename];
[zoomScroller addSubview:imageView];
self.view = zoomScroller;
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
return imageView;
}
The full project is at https://github.com/tomkincaid/ZoomScrollTest
I can test that the pinch zoom works by changing
ScrollerViewController *viewController = [[ScrollerViewController alloc] init];
to
ImageViewController *viewController = [[ImageViewController alloc] init];
viewController.filename = #"cat.jpg";
Its been quite a while that you posted your question. I bet you fixed it already yourself but I want to make sure other people can use your code.
However I downloaded your small GitHub project and found that you get the crash because you don't retain the ImageViewController's page1 and page2 in [ScrollerViewController viewDidLoad]. The views them selfs don't retain their controllers therefor the controllers get released after viewDidLoad in your case. Then when you pinch on the image scroll view it calls for its delegate but it is already deallocated.
To fix this I added two ImageViewController properties to the ScrollerViewController class and stored the controller objects there.
#interface ScrollerViewController ()
#property (strong) ImageViewController *page1;
#property (strong) ImageViewController *page2;
#end
In [ScrollerViewController viewDidLoad] I added at the end:
self.page1 = page1;
self.page2 = page2;
I hope that someone may find this information useful. Maybe you want to update your GitHub project so that it will compile and run.

Subview common to multiple view controllers is unresponsive

I am using a UIView to show busy/processing clue to User and components in loading view are not responding to user touches. Let me elaborate my problem.
I have around 3 UIViewControllers and based on situation one View from VieControllers would be displayed. I use following code to change current view
UIView *currentView = self.view;
UIView *theWindow = [currentView superview];
//Some other code
[currentView removeFromSuperview];
[self.view setFrame: [tileCountViewController.view bounds]];
[theWindow addSubview:tileCountViewController.view];
[theWindow setFrame:[tileCountViewController.view bounds]];
[theWindow setFrame: [[UIScreen mainScreen] bounds]];
I then have a Loading view which generally used to show busy/processing kinda of UI to User. I created Loading view in following way from nib file
- (id)init {
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
NSString *nibName = #"LoadingViewController";
if(UIInterfaceOrientationIsLandscape(orientation)) {
nibName = #"LoadingView-landscape";
}
else {
nibName = #"LoadingView";
}
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
UIView *portraitView = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
self = [super initWithFrame:portraitView.frame];
[self addSubview:portraitView];
[self bringSubviewToFront:portraitView];
return self;
}
In app delegate, I initialize loading view and add it to Main window
if(!loadingViewIndicator){
loadingViewIndicator = [[LoadingView alloc]init];
}
[[[UIApplication sharedApplication]keyWindow]addSubview:loadingViewIndicator];
loadingViewIndicator.hidden = YES;
Whenever I need to show loading/busy view, I simply load it like this
[loadingViewIndicator performSelectorOnMainThread:#selector(startLoadingViewIndicator:) withObject:startMsg waitUntilDone:NO];
- (void)startLoadingViewIndicator:(NSString *)startMsg{
self.hidden = NO;
[self.superview bringSubviewToFront:self];
[self.activityIndicator startAnimating];
[self.loadingMessage setText:startMsg];
self.loadingProgressView.progress = 0.0f;
[self refreshPosition];
}
Everything works fine but components in Loading view are not responding to touches. Any clue what is going wrong?
I just realized that problem is due to threading issue. I show this subview when User starts downloading any file. My earlier code was to directly start download
urlconnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately: YES];
I am now calling this method from another method using background thread as below
[self performSelectorInBackground:#selector(startDownload) withObject:nil];
And then I am adding runloop in download like
urlconnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately: YES];
CFRunLoopRun();
This works fine and my subview is responsive. It really took lots of time. The problem is there is clear code example to help to run download in background thread and at the same time make UI responsive.

Adding the scrollview created by photoscroller to a subview

I'm trying to modify Apple's PhotoScroller example to make the scrollview that is created into a subview instead of it being a view that takes up the entire screen. Any ideas on how this can be accomplished?
- (void)loadView
{
// Step 1: make the outer paging scroll view
CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
pagingScrollView.pagingEnabled = YES;
pagingScrollView.backgroundColor = [UIColor blackColor];
pagingScrollView.showsVerticalScrollIndicator = NO;
pagingScrollView.showsHorizontalScrollIndicator = NO;
pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
pagingScrollView.delegate = self;
// When I do this it fails
[self.view addSubview:pagingScrollView];
// Step 2: prepare to tile content
recycledPages = [[NSMutableSet alloc] init];
visiblePages = [[NSMutableSet alloc] init];
[self tilePages];
}
You just need to modify the frame of the scrollview to be positioned and sized how you want:
This is the line in the view controller that sets it up in the example
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
As an example here is a sample frame with some hardcoded values:
CGRect scrollFrame = CGRectMake(100,100,100,100);
pagingScrollView = [[UIScrollView alloc] initWithFrame:scrollFrame];
So, I found out that I was able to add the scrollView as a subview by changing the method from loadView to viewDidLoad.
I have no clue why that works, but it does. I'd love to know why that's the case however...

IOS - External (hdmi) output fills only half the screen except when coding view manually

So, as the title says, I have an hdmi out on the iPad and an observer registered for screen connections, upon connection the user chooses the res and a view is outputted.
However, if I load a view from a nib, or even from a programatic view controller, the ipad shows a landscape view in portrait (yes, both situations are set to landscape).
I.e.
ExternalViewController *ex = [[ExternalViewController alloc] init];
[externalWindow setRootViewController:ex];
does this:
If I create the view itself programatically. like so:
UIView *test = [[UIView alloc] initWithFrame:[externalScreen applicationFrame]];
[test setBackgroundColor:[UIColor whiteColor]];
UILabel *msgLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 40, 100, 30)];
msgLabel.text = #"External!";
[test addSubview:msgLabel];
It runs like some form of magical dream:
However I want the viewcontroller to load (and work!) so, StackOverflow, I ask you. has anyone come across this before?
EDIT: It does go without saying that common sensical answers do not get a bounty, I am after a fix, not a workaround. With my limited brain, all I can think to do is create a method that creates a view based on it's inputs and adds that as a subview of the external monitor, it is clear that this is a hack solution so a fix is appreciated! Thanks!
EDIT:
-(id)initWithFrame:(CGRect)_rect
{
rect = _rect;
if (self = [super init])
{
externalView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"105.png"]];
externalView.alpha = 0.0;
[externalView setFrame:rect];
[externalView setBackgroundColor:[UIColor yellowColor]];
self.view = [[UIView alloc] initWithFrame:rect];
self.view.backgroundColor = [UIColor whiteColor];
return self;
}
}
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:rect];
self.view.backgroundColor = [UIColor blackColor];
[self.view addSubview:externalView];
}
As requested, this is how I am loading the viewcontroller, initialising with the size of the external screen. Thanks
Just return NO in - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation in your UIViewController subclass.
// ugly, don't use this in real code
if ([UIScreen screens].count == 1) return; // just one screen, eww.
// getting the secondary screen
UIScreen *screen = [[UIScreen screens] objectAtIndex:1];
__block UIScreenMode *highestWidthMode = NULL;
[screen.availableModes enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UIScreenMode *currentModeInLoop = obj;
if (!highestWidthMode || currentModeInLoop.size.width > highestWidthMode.size.width)
highestWidthMode = currentModeInLoop;
}];
// setting to the highest resolution available
screen.currentMode = highestWidthMode;
NSLog(#"screen.currentMode = %#", screen.currentMode);
screen.overscanCompensation = UIScreenOverscanCompensationScale;
// initializing screen
secondWindow = [[UIWindow alloc] initWithFrame:[screen bounds]];
[secondWindow setScreen:screen];
// other view is a UIViewController, just remember to return NO in - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation or the iOS will rotate the frame.
UIViewController *vc = [[OtherView alloc] initWithNibName:#"OtherView" bundle:nil];
secondWindow.rootViewController = vc;
[secondWindow makeKeyAndVisible];

UILabel only shows up when placed in viewDidAppear of viewcontroller

I can't figure out why a UILabel only shows up when I create it from the viewDidAppear within my viewController. Here is my code so far:
Within AppDelegate:
CGRect viewBounds;
viewBounds.origin.x = 0;
viewBounds.origin.y = 0;
viewBounds.size.width = screenBounds.size.height;
viewBounds.size.height = screenBounds.size.width;
view = [[EAGLView alloc] initWithFrame: viewBounds];
overlayView = [[OverlayView alloc] initWithFrame: screenBounds];
overlayViewController = [[OverlayViewController alloc] init];
[overlayViewController setView:overlayView];
[window addSubview:view];
[window addSubview: overlayViewController.view];
[window makeKeyAndVisible];
[view start];
Within overlayViewController: (This function is successfully called, but the UILabel doesn't show up)
-(void)showText
{
NSLog(#"showText()");
textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 300.0f)];
textLabel.textColor = [UIColor whiteColor];
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.font = [UIFont fontWithName:#"Arial" size:30];
textLabel.text = [NSString stringWithFormat:#"TESTING!"];
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
}
Within overlayViewController: (Placing the above code into the viewDidAppear makes it show up from the beginning)
-(void)viewDidAppear:(BOOL)animated
{
textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 300.0f, 300.0f)];
textLabel.textColor = [UIColor whiteColor];
textLabel.backgroundColor = [UIColor clearColor];
textLabel.textAlignment = UITextAlignmentCenter;
textLabel.font = [UIFont fontWithName:#"Arial" size:30];
textLabel.text = [NSString stringWithFormat:#"TESTING!"];
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
[super viewDidAppear:animated];
}
Why would the UILabel not show up from within showText() function when it's called? I verified that the NSLog outputs to the console, yet the UILabel is not on the screen.
To give a little more context, this is an AR application. There is an EAGLView showing the feed of the camera on the screen. As I said, the UILabel, when placed in the viewDidLoad of overlayViewController, shows up the moment the app launches above the camera video feed. When placed inside the showText function, the UILabel doesn't show.
Any help would be appreciated! Thank you!
PS. To give more information, I have tried calling showText() in two ways:
Within my EAGLView.mm (which is where most of the AR functions are handled), I setup a notification as such:
[[NSNotificationCenter defaultCenter] postNotificationName:#"showTextOverlay" object:nil];
Then, within OverlayViewController.m, I placed an observer within ViewDidAppear (since ViewDidLoad doesn't seem to get called, but ViewDidAppear does...)
-(void)viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear");
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(showText)
name:#"showTextOverlay"
object:nil];
[super viewDidAppear:animated];
}
The selector of this observer calls showText(), which is also inside of OverlayViewController.
I next tried a second way:
Within EAGLView.mm, I got the application delegate and controllers directly as such:
ImageTargetsAppDelegate *delegate = (ImageTargetsAppDelegate *)[UIApplication sharedApplication].delegate;
OverlayViewController *controller = delegate.overlayViewController;
[controller showText];
But both ways still did not show any UILabel...
ANSWERED:
I figured this out. It turns out that the way the sample application is written, the updates to UIKit were not being called on the main thread. Therefore, I used the performSelectorOnMainThread when calling my showText...
Thank you everyone for your help!
One thing to think about is that showText ends with these lines:
[self.view addSubview:textLabel];
[self.view bringSubviewToFront:textLabel];
and then the next line is:
[overlayViewController setView:overlayView];
So, what would the value of self.view be in showText if the viewController does even set its view until the next line? You are likely adding a subview to a nil object.
Generally speaking, you should be doing things a little bit differently. The designated initializer for a view controller is initWithNibName:bundle rather than init so it is recommended that you use that.
More importantly, the OverlayViewController should either load its view from a .xib file or implement the loadView method to create its view. If you add the label inside of that method or in one of the methods called after it, like viewDidLoad, you will see the label.

Resources