how do i scroll through 100 photos in UIScrollView in IPhone - iphone

I'm trying to scroll through images being downloaded from a users online album (like in the facebook iphone app) since i can't load all images into memory, i'm loading 3 at a time (prev,current & next). then removing image(prev-1) & image (next +1) from the uiscroller subviews.
my logic works fine in the simulator but fails in the device with this error:
[CALayer retain]: message sent to deallocated instance
what could be the problem
below is my code sample
- (void)scrollViewDidEndDecelerating:(UIScrollView *)_scrollView
{
pageControlIsChangingPage = NO;
CGFloat pageWidth = _scrollView.frame.size.width;
int page = floor((_scrollView.contentOffset.x - pageWidth / 2) / pageWidth) + 1;
if (page>1 && page<=(pageControl.numberOfPages-3)) {
[self removeThisView:(page-2)];
[self removeThisView:(page+2)];
}
if (page>0) {
NSLog(#"<< PREVIOUS");
[self showPhoto:(page-1)];
}
[self showPhoto:page];
if (page<(pageControl.numberOfPages-1)) {
//NSLog(#"NEXT >>");
[self showPhoto:page+1];
NSLog(#"FINISHED LOADING NEXT >>");
}
}
-(void) showPhoto:(NSInteger)index
{
CGFloat cx = scrollView.frame.size.width*index;
CGFloat cy = 40;
CGRect rect=CGRectMake( 0, 0,320, 480);
rect.origin.x = cx;
rect.origin.y = cy;
AsyncImageView* asyncImage = [[AsyncImageView alloc] initWithFrame:rect];
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString:[pics objectAtIndex:index]];
[asyncImage loadImageFromURL:url place:CGRectMake(150, 190, 30, 30) member:memberid isSlide:#"Yes" picId:[picsIds objectAtIndex:index]];
[scrollView addSubview:asyncImage];
[asyncImage release];
}
- (void) removeThisView:(NSInteger)index
{
if (index<[[scrollView subviews] count] && [[scrollView subviews] objectAtIndex:index]!=nil) {
if ([[[scrollView subviews] objectAtIndex:index] isKindOfClass:[AsyncImageView class]] || [[[scrollView subviews] objectAtIndex:index] isKindOfClass:[UIImageView class]]) {
[[[scrollView subviews] objectAtIndex:index] removeFromSuperview];
}
}
}
For the record it works OK in the simulator, but not the iphone device itself.
any ideas will be appreciated.
cheers,
fred.

I guess your AsyncImageView class uses asynchronous connections to load the image (which is a good thing), so that means that when you add it as a subview of your scroll view, it might be "removedFromSuperview" before the image loads completely. Check in the implementation of your class if the delegates are properly nil'd when it's been dealloc'd, and also that any connections are cancelled and so on.
Pay attention to the fact that a class should never retain its delegate, an "assign" kind of property is perfect for these situations.
Some tips:
Instead of alloc/init'ing and release'ing so many instances while you scroll, try to keep three ivars in your class, and just change the "frame" property of the instances as you scroll. You will avoid lots of allocation and releases if the user scrolls fast, and that's a good thing on an iPhone.
Avoid direct ivar accesses like that in your code; use properties, there are many advantages to them beyond the simple "correctness" thing (KVO being one of them).

Related

UIAlertView Rendering Error

I have been working on an app for a couple of months now, but have finally run into an issue that I can't solve myself, and can't find anything on the internet to help.
I am using several normal UIAlertViews, in my app. Some have 2 buttons, some have 3 buttons, and a couple have 2 buttons and a text field. However all have the same issue. When you call [someAlertView show]; the alert view appears as normal, but then suddenly its graphics context seems to get corrupted as you can see from the screenshot.
This happens on both iPhone and iPad simulators (both 5.0 and 5.1), and happens on an iPad and iPhone4S device as well.
The image showing through is whatever happens to be behind the alertView.
The Alert still works, I can click the buttons, type in the text field, then when it dismisses the delegate methods are called correctly and everything goes back to normal. When the alertView appears again, the same thing happens.
The view behind the alert is a custom UIScrollView subclass with a content size of approximately 4000 pixels by 1000 with a UIImage as the background. The png file is mostly transparent, so is only about 80kB in memory size, and the phone is having no issues rendering it - the scroll view is still fully responsive and not slow.
It also has a CADisplayLink timer attached to it as part of the subclass. I have tried disabling this just before the alertView is shown, but it makes no difference so I am doubtful that is the issue.
This app is a partial rewrite of one I made for a university project, and that one could display UIAlertViews over the top of a scrollView of the same size and subclass without issue. The difference between this app and that one is that in my old app, I had subclassed UIAlertView to add extra things such as a pickerView, however I decided that I didn't like the way it looked so moved everything out of the alert and am just sticking with a standard UIAlertView.
This is how the alertView in the screenshot is called:
- (IBAction)loadSimulation:(id)sender {
importAlert = [[UIAlertView alloc] initWithTitle:#"Load Simulation" message:#"Enter Filename:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"Load", nil];
[importAlert setAlertViewStyle:UIAlertViewStylePlainTextInput];
[importAlert showPausingSimulation:self.simulationView]; //Calling [importAlert show]; makes no difference.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
[self hideOrganiser]; //Not an issue as the problem occurs on iPad as well.
}
}
With this being the categorised AlertView to add the ability to stop the scrollViews CADisplay link.
#interface UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView*)simulationView;
#end
#implementation UIAlertView(pauseDisplayLink)
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
[simulationView removeDisplayLink]; //displayLink needs to be removed from the run loop, otherwise it will keep going in the background and get corrupted.
[self show];
}
I get no memory warnings when this happens, so I am doubtful it is due to lack of resources.
Has anyone come across an issue like this before? If you need further information I can try to provide it, but I am limited in what code I can post. Any help would be appreciated, I've been trying to solve this for two weeks and can't figure it out.
Edit:
It appears that it is not the AlertView at all (or rather it is not just the alertView), as the problem goes away when I remove the scroll view behind it, so there must be some issue between the two. This is the code for my UIScrollView subclass:
.h file:
#import
#import
#class ECSimulatorController;
#interface UILogicSimulatorView : UIScrollView {
CADisplayLink *displayLink;
NSInteger _updateRate;
ECSimulatorController* _hostName;
}
#property (nonatomic) NSInteger updateRate;
#property (nonatomic, strong) ECSimulatorController* hostName;
- (void) removeDisplayLink;
- (void) reAddDisplayLink;
- (void) displayUpdated:(CADisplayLink*)timer;
- (void) startRunning;
- (void) stopRunning;
- (void) refreshRate:(NSInteger)rate;
- (void) setHost:(id)host;
- (void)setMinimumNumberOfTouches:(NSInteger)touches;
- (void)setMaximumNumberOfTouches:(NSInteger)touches;
#end
.m file:
#import "UILogicSimulatorView.h"
#import "ECSimulatorController.h"
#import <QuartzCore/QuartzCore.h>
#implementation UILogicSimulatorView
#synthesize updateRate = _updateRate;
#synthesize hostName = _hostName;
- (void)reAddDisplayLink {
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be re-added to the run loop after having been removed.
}
- (void)removeDisplayLink {
[displayLink removeFromRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //allows the display link to be removed from the Run loop without deleting it. Removing it is essential to prevent corruption between the games and the simulator as both use CADisplay link, and only one can be in the run loop at a given moment.
}
- (void)startRunning {
[self refreshRate:self.updateRate];
[displayLink setPaused:NO];
}
- (void)refreshRate:(NSInteger)rate {
if (rate > 59) {
rate = 59; //prevent the rate from being set too an undefined value.
}
NSInteger frameInterval = 60 - rate; //rate is the number of frames to skip. There are 60FPS, so this converts to frame interval.
[displayLink setFrameInterval:frameInterval];
}
- (void)stopRunning {
[displayLink setPaused:YES];
}
- (void)displayUpdated:(CADisplayLink*)timer {
//call the function that the snakeController host needs to update
[self.hostName updateStates];
}
- (void)setHost:(ECSimulatorController*)host;
{
self.hostName = host; //Host allows the CADisplay link to call a selector in the object which created this one.
}
- (id)initWithFrame:(CGRect)frame
{
//Locates the UIScrollView's gesture recogniser
if(self = [super initWithFrame:frame])
{
[self setMinimumNumberOfTouches:2];
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayUpdated:)]; //CADisplayLink will update the logic gate states.
self.updateRate = 1;
[displayLink setPaused:YES];
}
return self;
}
- (void)setMinimumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the minimum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMinimumNumberOfTouches:touches];
}
}
}
- (void)setMaximumNumberOfTouches:(NSInteger)touches{
for (UIGestureRecognizer *gestureRecognizer in [self gestureRecognizers])
{
if([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
{
//Changes the maximum number of touches to 'touches'. This allows the UIPanGestureRecogniser in the object which created this one to work with one finger.
[(UIPanGestureRecognizer*)gestureRecognizer setMaximumNumberOfTouches:touches];
}
}
}
#end
Well, I have managed to come up a solution to this. Really it is probably just masking the issue rather than finding the route cause, but at this point I will take it.
First some code:
#interface UIView (ViewCapture)
- (UIImage*)captureView;
- (UIImage*)captureViewInRect:(CGRect)rect;
#end
#implementation UIView (ViewCapture)
- (UIImage*)captureView {
return [self captureViewInRect:self.frame];
}
- (UIImage*)captureViewInRect:(CGRect)rect
{
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[self.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return screenShot;
}
#end
- (void)showPausingSimulation:(UILogicSimulatorView *)simulationView {
[simulationView stopRunning];
UIView* superView = simulationView.superview;
CGPoint oldOffset = simulationView.contentOffset;
for (UIView* subview in simulationView.subviews) {
//offset subviews so they appear when content offset is (0,0)
CGRect frame = subview.frame;
frame.origin.x -= oldOffset.x;
frame.origin.y -= oldOffset.y;
subview.frame = frame;
}
simulationView.contentOffset = CGPointZero; //set the offset to (0,0)
UIImage* image = [simulationView captureView]; //Capture the frame of the scrollview
simulationView.contentOffset = oldOffset; //restore the old offset
for (UIView* subview in simulationView.subviews) {
//Restore the original positions of the subviews
CGRect frame = subview.frame;
frame.origin.x += oldOffset.x;
frame.origin.y += oldOffset.y;
subview.frame = frame;
}
[simulationView setHidden:YES];
UIImageView* imageView = [[UIImageView alloc] initWithFrame:simulationView.frame];
[imageView setImage:image];
[imageView setTag:999];
[superView addSubview:imageView];
[imageView setHidden:NO];
superView = nil;
imageView = nil;
image = nil;
[self show];
}
- (void)dismissUnpausingSimulation:(UILogicSimulatorView *)simulationView {
UIView* superView = simulationView.superview;
UIImageView* imageView = (UIImageView*)[superView viewWithTag:999];
[imageView removeFromSuperview];
imageView = nil;
superView = nil;
[simulationView setHidden:NO];
[simulationView startRunning];
}
Then modifying the dismiss delegate method in my class to have this line:
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
[alertView dismissUnpausingSimulation:self.simulationView];
...
When the alert view is called, but before it is shown, I need to hide the simulator to prevent it corrupting the alert. However just hiding it is ugly as then all is visible behind is a empty view.
To fix this, I first make a UIImage from the simulator views graphics context. I then create a UIImageView with the same frame as the simulator and set the UIImage as its image.
I then hide the simulator view (curing the alert issue), and add my new UIImageView to the simulators superview. I also set the tag of the image view so I can find it later.
When the alert dismisses, the image view is then recovered based on its tag, and removed from its superview. The simulator is then unhidden.
The result is that the rendering issue is gone.
I know its too late for an answer to this question. Lately I had experianced this very same issue.
My Case:
Added couple of custom UIViews with background images and some controlls to the scroll view with shadow effect. I had also set the shadowOffset.
The Solution:
After some step by step analysis, I found out that setting the setShadowOpacity caused The rendering problem for me. When i commented that line of code, it cured the UIAlertView back to normal appearance.
More:
To make sure, I created a new project mimicing the original ui with shadowOpacity. But it didnt caused the rendering problem as i expected. So I am not sure about the root cause. For me it was setShadowOpacity.

iphone: error and pdf pages question

I am building a pdf viewer app. my skills are at medium LOL voodoo level now... I've got real far on my own with this app but I'm pretty stuck ether on my theory or on my code. My app has a paging scroll view that makes its self the length of the entire pdf document, then it shows the current page in another scrollview (ImageScrollView) which is its own class. ImageScrollView makes a UIView which does the CATiledLayer usual stuff and it all work fine! :)
My ImageScrollView shows the on-screen page (when you scroll to the next page ImageScrollView loads again CATiledLayer on-screen and you can see the tiles). I've been researching about how to get the pages left and right of the current page to preload (as I don't think having a pdf load as tiles on-screen is good for the user experience) but Im not to sure if I'm thinking about it correctly.
Maybe I should be making a left and right UIView that sit next to the onscreen UIView in ImageScrollView?
or maybe it has to do with recycling no-longer-visible pages as seen below (but I think I would still need views to the left/right and even still wont I need to recycle the views also??)
- (void)tilePages {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;//CGRect visibleBounds = CGRectMake(0.0f, 0.0f, 320.0f * [self pdfPageCount], 435.0f);
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
I changed the code to see what happened below (not sure) also I get error: initialization makes pointer from integer without a cast
- (void)tilePages:(NSUInteger) index {
// Calculate which pages are visible
CGRect visibleBounds = pagingScrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, [self pdfPageCount] - 1);
// Recycle no-longer-visible pages
for (ImageScrollView *page in visiblePages) {
if (page.index < firstNeededPageIndex || page.index > lastNeededPageIndex) {
// visible N/Ppages *start*
if (index == 1) {
ImageScrollView *Npage = Npage.index +1;
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index < 2 || index > [self pdfPageCount] -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];
Npage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Npage forIndex:index +1];
[pagingScrollView addSubview:Npage];
[visiblePages addObject:Npage];}
if (index == [self pdfPageCount] -1) {
ImageScrollView *Ppage = Ppage.index -1;
Ppage = [[[ImageScrollView alloc] init] autorelease];
[self configurePage:Ppage forIndex:index -1];
[pagingScrollView addSubview:Ppage];
[visiblePages addObject:Ppage];}
// visible N/Ppages *end*
[recycledPages addObject:page];
[page removeFromSuperview];}}
[visiblePages minusSet:recycledPages];
// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
// recycled N/Ppages *start*
if (index == firstNeededPageIndex +1) {
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index < firstNeededPageIndex +2 || index > lastNeededPageIndex -2) {
ImageScrollView *Ppage = Ppage.index -1;
ImageScrollView *Npage = Npage.index +1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];
[recycledPages addObject:Npage];
[Npage removeFromSuperview];}
if (index == lastNeededPageIndex -1) {
ImageScrollView *Ppage = Ppage.index -1;
[recycledPages addObject:Ppage];
[Ppage removeFromSuperview];}
// recycled N/Ppages *end*
if (![self isDisplayingPageForIndex:index]) {
ImageScrollView *page = [self dequeueRecycledPage];
if (page == nil) {
page = [[[ImageScrollView alloc] init] autorelease];}
[self configurePage:page forIndex:index];
[pagingScrollView addSubview:page];
[visiblePages addObject:page];}}}
Could I store 3 pdf pages in an NSIndex? eg: previousPageToRecycle, previousPage, currentPage, nextPage, nextPageToRecycle
I'm unsure of how to do this.
ImageScrollView *Ppage = Ppage.index -1;
Ppage.index -1 is an integer. You're assigning it to a variable of type ImageScrollView*, which is supposed to magically turn it into a pointer. This is probably a mistake.
The first bit of code is much more readable, and looks like it's vaguely on the right track.
The second bit of code is completely unreadable due to lack of sane indenting. There are also some problems:
There are far too many special cases. Simplify your logic; it makess your code easier to get right and easier to maintain.
There is a lot of duplicated code. This is related to the special-casing.
I can't for the life of me figure out why firstNeededPageIndex+1 and lastNeededPageIndex-1 are special.
I would not use an NSSet for so few pages. I don't think being able to use minusSet: is a significant advantage over just using removeObject: (and if you're worried about performance, just use removeObjectIdenticalTo:)
I'm not sure what you mean by "storing pages in an NSIndex"; do you mean CFIndex or NSInteger?
And now, an overall answer:
In general, you're on the right track, but you need to load up to five pages:
The current page, i.
Pages i-1 and i+1. The user can start scrolling at any time; you don't want the display to lag while you render some tiles.
Pages i-2 and i+2. Bouncy-scrolling means if the user scrolls from i to i+1, it will show a few pixels of i+2. You don't want to load pages during the scroll animation, or it'll appear to lag (unless you can make it load in a background thread somehow). Note that if the user scrolls from i to i+1 to i+2, it'll "bounce" at i+3. You can load more, but I consider this uncommon enough that a little lag is justified.
I implemented this using a ring-buffer-like array of size 8; page i is stored in ring[i%8] if it is loaded. Since I only need to have 5 pages at a time, 8 is plenty; admittedly there's a largeish pile of ring-buffer-management code that you need to get right (mostly it just takes a while to code).
I also just used a "current page", loading i±1 during a scroll (hopefully they're already loaded) and i±2 at viewDidAppear: and at the end of a scroll (scrollViewDidEndDragging:willDecelerate: with decelerate=NO, and scrollViewDidEndDecelerating:).
You might also want to hold on to views a little longer (preload i±2 but keep i±3 if they happen to be loaded), otherwise views will be needlessly reloaded if the user moves back and forth during a scroll (laaaaaag).
If you show a page number, you might also want to add some hysteresis. I only changed pages when the user scrolled 2/3 of the way into the next page (that means to toggle between two pages, the user has to move back and forth by 1/3 page). If you implement hysteresis, the previous paragraph isn't such a big deal.

pragmatically scroll UIScrollView upon shake gesture

HI,
I have a view that has three UIScrollViews on the screen. I want to randomly scroll the UIScrollViews to different positions whenever a user shakes the iPhone, but I am unable to get it done.
I have implemented the following in my ViewController class to detect and handle the shake gesture, the 3 UIScrollViews also belong to the same class. The shake gesture is detected, but the UIScrollViews do not change. What am I doing Wrong??
i have tried both motionBegan and motionEnded.
-(BOOL)canBecomeFirstResponder {
return YES;
}
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[self resignFirstResponder];
[super viewWillDisappear:animated];
}
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake)
{
int randomTag = arc4random() % [dirContents count];
CGRect nextImageView = [[scrollView1 viewWithTag:2] frame];
[scrollView1 scrollRectToVisible:nextImageView animated:YES];
randomTag = arc4random() % [dirContents count];
nextImageView = [[scrollView2 viewWithTag:4] frame];
[scrollView2 scrollRectToVisible:nextImageView animated:YES];
randomTag = arc4random() % [dirContents count];
nextImageView = [[scrollView3 viewWithTag:4] frame];
[scrollView3 scrollRectToVisible:nextImageView animated:YES];
NSLog(#"Shake Detected End");
}
}
Thank You
Have you tried using SetContentOffset instead of scrollRectToVisible yet?
if the images in your tableView are of equal height the offset per "Element" is always the same.
[scrollView3 setContentOffset:yourRandomOffsetInPixels animated:YES];
maybe this works.
also consider, that The Problem might be that your shake-detection Method runs on a separate Thread this would mean that you have to call your motionEnded Method on the mainthread like so:
[self performSelectorOnMainThread:#selector(motionEnded) withObject:nil waitUntilDone:NO];
Did you check your nextImageView variable to see if it was correct ?
Further more if you are trying will have the motion of a slot machine, I would recommend you to use UITableView instead of doing it by yourself with scrollView
Just one quick question. In your example code, you generate a random tag:
randomTag = arc4random() % [dirContents count];
but then you use a specific tag value (4 in this case)? I assume it still doesn't work when you use the randomTag value? and that you were just doing some testing?
nextImageView = [[scrollView2 viewWithTag:4] frame];

Remove UIWebView Shadow?

Does anyone know if its possible to remove the shadow that is placed on the UIWebView window?
Example: http://uploadingit.com/files/1173105_olub5/shadow.png
If its possible how do you do it?
Thanks
This is a cleaner alternative to "Nikolai Krill" solution. This only hides UIImageViews within the UIWebView and not the UIWebBrowserView.
for (UIView *view in [[[webView subviews] objectAtIndex:0] subviews]) {
if ([view isKindOfClass:[UIImageView class]]) view.hidden = YES;
}
Thanks
James
the small for loop is very dangerous because it can crash if apple changes the number of the subviews.
this way it does at least not crash when something changes:
if ([[webView subviews] count] > 0)
{
for (UIView* shadowView in [[[webView subviews] objectAtIndex:0] subviews])
{
[shadowView setHidden:YES];
}
// unhide the last view so it is visible again because it has the content
[[[[[webView subviews] objectAtIndex:0] subviews] lastObject] setHidden:NO];
}
There is a private method with the selector setAllowsRubberBanding: that takes a BOOL value. If passed NO, you will not be able to scroll the web view past the top or bottom of the content area, but will still let you scroll through the web view normally. Unfortunately, this method IS private, and your app will likely not be allowed onto the store if you use it.
You could, however, potentially try and extract the method implementation and bind it to a different selector that you've created, using the dynamic nature of Objective-C's runtime.
Still, the method is private and may no longer exist in future versions of the OS. If you still want to try, here's some sample code that will extract the setAllowsRubberBanding: method implementation and call it for you.
static inline void ShhhDoNotTellAppleAboutThis (UIWebView *webview)
{
const char *hax3d = "frgNyybjfEhooreOnaqvat";
char appleSelName[24];
for (int i = 0; i < 22; ++i)
{
char c = hax3d[i];
appleSelName[i] = (c >= 'a' && c <= 'z') ? ((c - 'a' + 13) % 26) + 'a' : ((c - 'A' + 13) % 26) + 'A';
}
appleSelName[22] = ':';
appleSelName[23] = 0;
SEL appleSEL = sel_getUid(appleSelName);
UIScrollView *scrollView = (UIScrollView *)[webview.subviews objectAtIndex:0];
Class cls = [scrollView class];
if (class_respondsToSelector(cls, appleSEL) == NO)
{
return;
}
IMP func = class_getMethodImplementation(cls, appleSEL);
func(scrollView, appleSEL, NO);
}
Please note that this will probably still get caught by Apple's static analyzer if you choose to submit an app using this code to the AppStore.
Here is a Swift function that gets rid of the shadow in a UIWebView in iOS 9. It’s safer than any alternative I’ve seen on SO because everything in it is in Apple documentation, and it specifically alters the shadow property (as opposed to hiding the entire view or some other property of the view).
func removeShadow(webView: UIWebView) {
for subview:UIView in webView.scrollView.subviews {
subview.layer.shadowOpacity = 0
for subsubview in subview.subviews {
subsubview.layer.shadowOpacity = 0
}
}
}
You can always access the subviews property of a UIView(documentation). Every UIView has a layer property that is a CALayer (documentation). Every CALayer has shadowOpacity (documentation).
Caveats:
You might have to go deeper in navigating the view hierarchy through subviews depending on your situation.
This works as long as you don’t want any shadows anywhere in the web view controller. If you have a view where you want to keep the shadow (other than the default UIWebView shadow), then you could add an if-check to identify that view and not set that view’s layer’s shadowOpacity to zero.
According to Apple “For complex views declared in UIKit and other system frameworks, any subviews of the view are generally considered private and subject to change at any time. Therefore, you should not attempt to retrieve or modify subviews for these types of system-supplied views. If you do, your code may break during a future system update” . . . in other words, UIWebView can change and its not recommended to be digging into these subviews. However, digging into the UIWebView is the only way to get rid of the shadow and this is a relatively safe way to do it.
This can be done without use of private APIs. All you need to do is hide each UIImageView with the shadow in it. Heres the code:
for (int x = 0; x < 10; ++x) {
[[[[[webView subviews] objectAtIndex:0] subviews] objectAtIndex:x] setHidden:YES];
}
Try this
func webViewDidFinishLoad(_ webView: UIWebView) {
for shadowView in self.webView.scrollView.subviews {
if !shadowView.isKind(of: UIImageView.self) {
shadowView.subviews[0].layer.shadowColor = UIColor.clear.cgColor
} else {
shadowView.layer.shadowColor = UIColor.clear.cgColor
}
}
}
Traverse all subviews, the UIImageViews whose image is only 1 pixel wide are shadow images, you can hide them.
- (void)hideShadows {
[webview traverseViewsWithBlock:^(UIView *view) {
UIImageView *imgView = ([view isKindOfClass:[UIImageView class]] ? (UIImageView*)view : nil;
// image views whose image is 1px wide are shadow images, hide them
if (imgView && imgView.image.size.width == 1) {
imgView.hidden = YES;
}
}];
}
traverseViewsWithBlock does what it looks like:
- (void)traverseViewsWithBlock:(void (^)(UIView* view))block
{
block(self);
for (id subview in self.subviews) {
[subview traverseViewsWithBlock:block];
}
}
I looked at the class properties and didn't find anything there but I can think of two "cover up" strategies:
1. You can use another view (parent of the web view) to clip the webview bounds.
2. You can add another view on top of the webview to cover the needed area with a color that matches the background, you can use an uiimage with a transparent area in the center.
By the way I don't like this standard background of the table views :P, but changing it can be a pain in the ass :P
You have to be careful, the scroll indicators are UIImageViews as well.
I'll improve my code, but here's a basic subclassed solution:
http://forrst.com/posts/A_tiny_UIWebView_hack_remove_shadows_from_behi-gzH
The easiest way to hide scroll indicators and transparent the web view here in UIWebView
To remove the scrolls.
for(UIView *view in webView.subviews){ 
     if ([view isKindOfClass:[UIScrollView class]]) {
          UIScrollView *sView = (UIScrollView *)view;
          //to hide verticalScroller
          sView.showsVerticalScrollIndicator = NO;
sView.showsVerticalScrollIndicator = NO;
     }
}
What about a category on UIWebView like this:
- (BOOL)showsScrollShadows
{
for(UIImageView *imageView in [self imageViewsWithShadows])
{
if(imageView.hidden)
{
return NO;
}
break;
}
return YES;
}
- (void)setShowsScrollShadows:(BOOL)showsScrollShadows
{
[[self imageViewsWithShadows] makeObjectsPerformSelector:#selector(setHidden:) withObject:#(!showsScrollShadows)];
}
- (NSArray *)imageViewsWithShadows
{
NSArray *potentialShadowImageViews = (self.subviews.count > 0) ? [self.subviews[0] subviews] : nil;
if(potentialShadowImageViews.count > 0)
{
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings)
{
return [evaluatedObject isKindOfClass:[UIImageView class]];
}];
return [potentialShadowImageViews filteredArrayUsingPredicate:predicate];
}
return nil;
}
I've had a look around and can't see anything related to it. Apart from masking it with a view or clipping it somehow, the only thing I can think of is to loop through all of the UIWebView subviews (and sub-subviews etc.) and see if you can see anything there!
I may be wrong, but I think the shadow only shows up when we scroll the webview doesn't it ?
In that case, do you want to prevent the scrolling or really hide the shadow ? I don't know any tips that would hide the shadow. To disable the scrolling, I would setUserInteractionEnabled to NO.
I added a recursive method as a category to the UIView object so that it will do a depth-first walk of the subviews of the method's receiving view, hiding any UIImageView subclasses it finds. It will not crash if there are no subviews. The -apply: method is from BlocksKit. You could rewrite this function not to use it, but the block is applied in parallel to each element of the receiving array, so it's pretty fast.
#implementation UIView (RemoveShadow)
- (void)removeShadow {
if (self.subviews.count == 0 && [self isKindOfClass:[UIImageView class]]) {
self.hidden = YES;
} else if (self.subviews.count > 0) {
[self.subviews apply:^(id sender) {
[(UIView *)sender removeShadow];
}];
}
}
#end
if (UIDevice.currentDevice.systemVersion.intValue < 7)
for (UIImageView *imageView in webView.scrollView.subviews)
if ([imageView isKindOfClass:[UIImageView class]] && imageView.image.size.width == 1)
imageView.hidden = YES;

autorelease pool

I was hoping someone could help me with a memory issue on the iPhone.
I have a scrollview that is part of a nav controller.
Whenever I first push the nav controller and scroll through the scrollview (adding images as I go), memory is allocated in huge chunks (say 1mb each).
If I rotate the display a couple of times, the memory is freed, and everything is fine: The scrollview works correctly and the memory returns to where it should be (around 1mb, looking at the Net alloc in Instruments).
How can I keep the memory from going wild, or free it up during use of the scrollview, just as the device rotation does?
Here is the code snippet that is called to load the scrollview page:
- (void)loadPage:(int)page isCurrent:(BOOL)isCurrent {
if (page < 0) return;
if (page >= totalRows) return;
picViewController *controller = [[picViewController alloc] init];
if ((NSNull *)[viewControllers objectAtIndex:page] == [NSNull null]) {
NSString *fullPath = [self fullPath];
if([fileManager fileExistsAtPath:fullPath]) {
currentImage=[UIImage imageWithContentsOfFile:fullPath];
[controller setImg:currentImage];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
else {
AsyncImageView* asyncImage = [[AsyncImageView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
asyncImage.tag = 8;
[asyncImage loadImageFromName:imageName withURL:url];
[controller.view addSubview:asyncImage];
[asyncImage release];
[viewControllers replaceObjectAtIndex:page withObject:controller];
}
if (nil == controller.view.superview) {
CGRect frame = scrollView.frame;
frame.origin.x = frame.size.width * page;
frame.origin.y = 0;
controller.view.frame = frame;
[scrollView addSubview:controller.view];
}
[controller release];
}
}
I don't see where in your code you're actually purging off-screen pages.
Your code is somewhat like Apple's PageControl example, right?
What I do is loop through the pages each time a page is "activated" and purge pages more than 1 or 2 screens away. (1 for without bounce scrolling, 2 for with.)
- (void)updateCachedPages {
int active = pageControl.currentPage;
int count = pageControl.numberOfPages;
for ( int i = 0; i < count; i++ ) {
if ( abs( active - i ) <= 2 ) {
[self loadPage:i];
}
else {
[self purgePage:i];
}
}
}
- (void)purgePage:(int)page {
if ((page < 0) || (page >= [_myObjects count])) return;
MyControllerClass *controller = [_viewControllers objectAtIndex:page];
if ((NSNull *)controller != [NSNull null]) {
[_viewControllers replaceObjectAtIndex:page withObject:[NSNull null]];
//NSLog( #"Purged page %d", page );
}
}
Call updateCachedPages in scrollViewDidEndDecelerating, your PageControl's page change action, and viewWillAppear.
does your controller pay attention to memory low notifications? You could flush any images which aren't visible when that's received, or do any other releasing you think relevant.
OK, I wasn't freeing all the memory after all.
Once I found a class instance not being freed properly, and made sure tewha's purge code was working, everything is golden.
Thanks for the quick help. This forum is awesome BTW.