31

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

15 Answers15

71

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

jodm
  • 2,607
  • 3
  • 25
  • 40
  • Did someone already pass this through the appStore validation process ? – Francescu Sep 15 '10 at 15:56
  • Yes, an application I created uses this code and went through the validation process first time on Monday. – jodm Sep 15 '10 at 20:01
  • After testing, I suppose this solution only works for iOS 4.x? – alexleutgoeb Dec 09 '10 at 10:33
  • Thanks Martin, my comment from 2010 was because of 3.x compatibility reasons in those times ;) – alexleutgoeb Jun 20 '12 at 07:20
  • 4
    To keep the scroll bar indicators, use this code: `if([wview isKindOfClass:[UIImageView class]] && wview.frame.size.width != 7) { wview.hidden = YES; }` rather than the if statement in this answer. – Hank Brekke Nov 24 '12 at 15:39
  • Works on iOS 6 too (just checked), and doesn't crash on iOS 7 (perhaps will break in future releases). So at the very least it's safe to use for 5 <= iOS <= 7.0 – Nicolas Miari Dec 17 '13 at 07:30
14

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];
}
David Snabel-Caunt
  • 57,804
  • 13
  • 114
  • 132
denis2342
  • 521
  • 4
  • 10
5

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.

PfhorSlayer
  • 1,337
  • 9
  • 14
5

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:

  1. You might have to go deeper in navigating the view hierarchy through subviews depending on your situation.
  2. 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.
  3. 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.
devney
  • 103
  • 1
  • 2
  • 8
4

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];
}
LinusGeffarth
  • 27,197
  • 29
  • 120
  • 174
Nikolai Krill
  • 414
  • 3
  • 4
  • 1
    This could crash if there a no subviews – parceval Aug 04 '10 at 11:17
  • This works for us here thanks! However, what if Apple's code changes? What bugs me is that if you want formatted text, you are recommended to use the UIWebView... But a UIWebView doesn't look the same! – jowie Aug 19 '10 at 11:20
  • Holy crap... Never ever assume this kind of stuff in opaque view hierarchies! – Daniel Rinser Mar 12 '13 at 09:03
3

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
        }
    }
}
Berlin Raj
  • 124
  • 6
2

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];
    }
}
rrrus
  • 323
  • 2
  • 6
1

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

user41806
  • 559
  • 4
  • 6
1

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;
         }   
    }  
damithH
  • 5,148
  • 2
  • 27
  • 31
1

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;
}
Kukosk
  • 2,892
  • 1
  • 27
  • 30
1

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

e.James
  • 116,942
  • 41
  • 177
  • 214
Geri Borbás
  • 15,810
  • 18
  • 109
  • 172
0

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
Jacob
  • 926
  • 1
  • 14
  • 25
0

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!

Michael Waterfall
  • 20,497
  • 27
  • 111
  • 168
0

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.

Unfalkster
  • 683
  • 5
  • 12
0
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;
Friend_LGA
  • 71
  • 2