Search code examples
iosobjective-cuitableviewdelegatesuiscrollviewdelegate

Remove code duplication from 2 implementations of delegate methods


I've got a ViewController class and a View class. The ViewController has a UIScrollView and the View has a UITableView, and both are delegates of UIScrollViewDelegate.

Both the View and the ViewController make use of the scrollViewDidScroll: delegate method, and both have the same code in it. It's clearly a duplication so I'd like to fix this.

The problem is I've no idea how.

I can't extract the functionality into a base class and I also don't think I can have a separate class implementing the method, whose instances I could then use (it's possible in Android but not in iOS). I was thinking of using blocks but don't see how they'd work here.

The method itself performs some changes on the UI of the ViewController/View; here's its body:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {

    float multiplierYAnimationStop = [Utilities getFloatValueFromPlistFile:@"layout_values" forDictionaryKey:@"scroll_background_y_stop_multiplier"];
    float currentScrollOffsetY = scrollView.contentOffset.y;

    // No case for further back than the bottom of the screen (lower than 0)
    // and if it's higher than where it should stop, keep it at that point
    if (currentScrollOffsetY > screenHeight * multiplierYAnimationStop) {

        currentScrollOffsetY = screenHeight * multiplierYAnimationStop;
    }

    // Scale the background
    float newBackgroundScale = 1 - currentScrollOffsetY / screenHeight;

    if (newBackgroundScale < 0.75f) {

        newBackgroundScale = 0.75f;
    }

    [self scaleBackgroundToNewScale:newBackgroundScale];

    // Move the UILabel with the title and the Button
    float newLabelCenterY = originalLabelTitleCentreY - currentScrollOffsetY;
    float newButtonCenterY = originalButtonDownArrowCentreY - currentScrollOffsetY;
    self.labelHeadline.center = CGPointMake(self.labelHeadline.center.x, newLabelCenterY);
    self.buttonDownArrow.center = CGPointMake(self.buttonDownArrow.center.x, newButtonCenterY);

    // Blur the UILabel with the title and the Button
    float newHeadlineAlpha = 1 - currentScrollOffsetY / 100.0f;

    if (newHeadlineAlpha < 0.0f) {

        newHeadlineAlpha = 0.0f;
    }

    [self.labelHeadline setAlpha:newHeadlineAlpha];
    [self.buttonDownArrow setAlpha:newHeadlineAlpha];

    if (newHeadlineAlpha < 0.95f) {

        [self.buttonDownArrow setEnabled:NO];
    }
    else {

        [self.buttonDownArrow setEnabled:YES];
    }

    // Set the new alpha for the background overlay
    // (no bigger than scroll_overlay_max_opacity, should be scroll_overlay_max_opacity once the offset hits the stop point)
    float maxOpacity = [Utilities getFloatValueFromPlistFile:@"layout_values" forDictionaryKey:@"scroll_overlay_max_opacity"];
    float subtractionFactor = 1.0f - maxOpacity;

    float newOverlayAlpha = currentScrollOffsetY / screenHeight / multiplierYAnimationStop - subtractionFactor;

    if (newOverlayAlpha > maxOpacity) {

        newOverlayAlpha = maxOpacity;
    }

    [self.viewOverlay setAlpha:newOverlayAlpha];

    // If set, move the background vertically on scroll
    if ([Utilities getBoolValueFromPlistFile:@"layout_values" forDictionaryKey:@"is_scroll_background_movable"]) {

        if (newBackgroundScale == 0.75f) {

            self.imageBackground.center = CGPointMake(self.imageBackground.center.x, self.imageBackground.center.y - (currentScrollOffsetY - 0.25f * screenHeight));
        }
    }
}

Solution

  • Create a function that encapsulates what you want, and pass any needed variables (possibly including self) to it.

    If you have more duplicated logic between your objects, you can pull that together into a strategy object that both object have an instance of (rather than inheriting from). Essentially this would be a delegate for your delegate (which is fine). Or if you have a collection of related shared functions you could make them class methods of some strategy class. But if it's just a single method like this, a function is fine.