detect changes to UIWebView's scroll view's contentSize
Asked Answered
C

2

17

I'm trying to set a UIView at the bottom of the content of a UIScrollView, do to so I set the view's position to the scrollview's contentsize height. But my scrollview is a subview of a UIWebView so when images are loaded, the contentsize height changes and my view which should be at the bottom of the scrollview ends up in the middle...

So I am looking for a way to be notified when the scrollview's contentsize changes. I've tried to subclass it and change the setter for contentsize to send a NSNotification:

@implementation UIScrollView (Height)

-(void)setContentSize:(CGSize)contentSize
{
    _contentSize=contentSize;
    [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:@"scrollViewContentSizeChanged" object:nil]];
}

@end

But on compile I get and error saying:

"_OBJC_IVAR_$_UIScrollView._contentSize", referenced from: -[UIScrollView(Heigth) setContentSize:] in MyClass.o ld: symbol(s) not found for architecture armv7

Any idea how the setter should be subclassed ?

Thanks !

Calcaneus answered 8/11, 2012 at 18:52 Comment(0)
C
32

Perhaps you can use key-value observing (KVO) to detect changes to the content size. I haven't tried it, but the code should look like this:

static int kObservingContentSizeChangesContext;

- (void)startObservingContentSizeChangesInWebView:(UIWebView *)webView {
    [webView.scrollView addObserver:self forKeyPath:@"contentSize" options:0 context:&kObservingContentSizeChangesContext];
}

- (void)stopObservingContentSizeChangesInWebView:(UIWebView *)webView {
    [webView.scrollView removeObserver:self forKeyPath:@"contentSize" context:&kObservingContentSizeChangesContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context == &kObservingContentSizeChangesContext) {
        UIScrollView *scrollView = object;
        NSLog(@"%@ contentSize changed to %@", scrollView, NSStringFromCGSize(scrollView.contentSize));
    } else {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

If that doesn't work, you may need to swizzle the setContentSize: method. Method swizzling lets your replacement method call the original method, which is what you need to do to pass the new content size on to the scroll view.

You can read more about method swizzling here: http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html

I think this is the most popular code for swizzling: https://github.com/rentzsch/jrswizzle

Churchwarden answered 8/11, 2012 at 19:4 Comment(3)
@rob mayoff can you share, this on swift3?Kratzer
@rob mayoff with WKWebview in swift 4, the notification gets sent multiple times when I scroll. The content size isn't actually changing though, but scrolling repeatedly sends the notification. Is this expected behavior?Spelter
Why do you think using Swift has any effect on how often WKWebView updates contentSize? Anyway, if it’s a problem for you, store the prior contentSize in an instance variable and only take action if the new size is actually different.Churchwarden
T
3

Your approach is half correct. You can surely override an existing method through a category, what you cannot do, though is accessing an ivar of the class.

In this case, what you need is method swizzling: you override setContentSize while at the same time keeping a reference to the original implementation of the method, so you can call it to set _contentSize value.

Here is the code that you could use, with comments:

@implementation UIScrollView (Height)

// -- this method is a generic swizzling workhorse
// -- it will swap one method impl with another and keep the old one
// under your own impl name
+ (void)swizzleMethod:(SEL)originalSel andMethod:(SEL)swizzledSel {

  Method original = class_getInstanceMethod(self, originalSel);
  Method swizzled = class_getInstanceMethod(self, swizzledSel);
  if (original && swizzled)
     method_exchangeImplementations(original, swizzled);
  else
    NSLog(@"Swizzling Fault: methods not found.");

}    

//-- this is called on the very moment when the categoty is loaded
//-- and it will ensure the swizzling is done; as you see, I am swapping
//-- setContentSize and setContentSizeSwizzled;
+ (void)load {
  [self swizzleMethod:@selector(setContentSize:) andMethod:@selector(setContentSizeSwizzled:)];
}

//-- this is my setContentSizeSwizzled implementation;
//-- I can still call the original implementation of the method
//-- which will be avaiable (*after swizzling*) as setContentSizeSwizzled
//-- this is a bit counterintuitive, but correct!
- (void)setContentSizeSwizzled:(CGSize)contentSize
{
  [self setContentSizeSwizzled:contentSize];
  [[NSNotificationCenter defaultCenter] postNotification:[NSNotification    notificationWithName:@"scrollViewContentSizeChanged" object:nil]];
}

@end

Hope it helps.

Tripura answered 8/11, 2012 at 19:12 Comment(3)
Thank you very much for this nice answer. But I have a question. If I have multiple instances for the object, how can I detect which instance is calling this method?Metcalfe
@OlcayErtaş: you could use postNotificationName:object:userInfo: and send self as userInfo or whatever other useful info: developer.apple.com/reference/foundation/nsnotificationcenter/…Tripura
I did the same. Thank you for response.Metcalfe

© 2022 - 2024 — McMap. All rights reserved.