4

I am using a MPTableViewAdPlacer to implement native ads in iOS UITableView. When initialising the MPTableViewAdPlacer, it asks for a MPStaticNativeAdRendererSettings, which needs to implement a viewSizeHandler. However, this is before any ads are fetched, as the name suggested "Static" Native Ad. I am trying to implement one where the cell height can be calculated after getting the adData, such as the title, image...etc. I've been trying to find a way to implement a dynamic cell height but all the sample app, instructions provided by twitter only shows the static height implementation.

Code below:

-(void)setupAdPlacer {

    MPNativeAdRequestTargeting *targeting = [MPNativeAdRequestTargeting targeting];
    targeting.location = [[CLLocationManager alloc] init].location;

    targeting.desiredAssets = [NSSet setWithObjects: kAdMainImageKey, kAdCTATextKey, kAdTextKey, kAdTitleKey, nil];

    MPStaticNativeAdRendererSettings *settings = [[MPStaticNativeAdRendererSettings alloc] init];

    settings.renderingViewClass = [REPostListViewMoPubAdCell class];
    settings.viewSizeHandler = ^(CGFloat maximumWidth) {
    return CGSizeMake(maximumWidth, 312.0);

    // STATIC HEIGHT
    };

    MPNativeAdRendererConfiguration *config = [MPStaticNativeAdRenderer rendererConfigurationWithRendererSettings:settings];

    self.adPlacer = [MPTableViewAdPlacer placerWithTableView:self.tableView viewController:self adPositioning:positioning rendererConfigurations:@[config]];
    self.adPlacer.delegate = self;

    [self.adPlacer loadAdsForAdUnitID:@"xxxxxxxxxxx" targeting:targeting];
}
Jacky Wang
  • 618
  • 7
  • 27
  • You can define advertisement's size by implementing `+ (CGSize)sizeWithMaximumWidth:(CGFloat)maximumWidth;` method in your cell's class which is implementing `` protocol. – Vatsal K May 05 '16 at 05:50
  • @VatsalK But sizeWithmaximunWidth is a class static function, how do you get the MPNativeAd's properties in that function? – Jacky Wang May 05 '16 at 05:54
  • `+ (CGSize)sizeWithMaximumWidth:(CGFloat)maximumWidth { if (iPhone4 || iPhone5) { return CGSizeMake(maximumWidth, 305 + 5); } else if (iPhone6) { return CGSizeMake(maximumWidth, 330 + 8); } else { return CGSizeMake(maximumWidth, 355 ); } return CGSizeMake(maximumWidth, 295 + 15); }` I have implemented this. in my cell's class which was implementing protocol. – Vatsal K May 05 '16 at 05:59
  • @VatsalK, I understand your implementation. but you are returning static values "305 + 5", "330 + 8". – Jacky Wang May 05 '16 at 06:04
  • I'm talking about dynamically using the AD's property, such as if the text of the body is too long, I would make the the height taller to fit the text provided by the ad. – Jacky Wang May 05 '16 at 06:04
  • Or if the image is huge, then it would need a taller cell height to fit it. – Jacky Wang May 05 '16 at 06:05
  • As far as advertisement documents concern, advertisement's size would be proportional, you don't need to worry about the explicit sizes of each ads. You needs to make sure that image is not getting squish or crop at any portion. You will get image of size `Main Image: large image, such as screenshot of the game (1200x627 pixels)`. You can downsize the `uiimageview` proportionally. [check this](https://dev.twitter.com/mopub/ad-formats/native-ads-setup) and [this](https://dev.twitter.com/mopub/native/native-ads-best-practices) – Vatsal K May 05 '16 at 06:12
  • @VatsalK Could you please elaborate more on what you said? So when I set up my imageView, how do I lay it out? – Jacky Wang May 05 '16 at 06:26
  • if I get a long image vs a short one, how would my cell know the height of the cell to return for the sizeWithMaxWidth? – Jacky Wang May 05 '16 at 06:30
  • @JackyWang Would you please suggest any tutorial for how to implement Facebook Native ads in Mopub SDK ? I think Mopub official documentation is outdated. – Maniganda saravanan Aug 31 '16 at 06:42

3 Answers3

5

You need to decide the height and width based on your screen and tableview. I have setup all the components at runtime and working very well.

I am setting up mopub using.

-(void)setUpMopPubAd
{
    MPServerAdPositioning *positioning = [[MPServerAdPositioning alloc] init];
    self.placer = [MPTableViewAdPlacer placerWithTableView:tableViewContent viewController:self adPositioning:positioning defaultAdRenderingClass:[MoPubAdTableViewCell class]];
    MPNativeAdRequestTargeting *targeting = [MPNativeAdRequestTargeting targeting]; targeting.desiredAssets = [NSSet setWithObjects:kAdIconImageKey, kAdMainImageKey, kAdCTATextKey, kAdTextKey, kAdTitleKey, nil];
    [self.placer loadAdsForAdUnitID:kMoPubKey];
    [tableViewContent mp_setDataSource:self];
    [tableViewContent mp_setDelegate:self];
}

I have created tableviewcell for MoPubAd.

MoPubAdTableViewCell.h

@interface MoPubAdTableViewCell : UITableViewCell<MPNativeAdRendering>

@property (strong, nonatomic) IBOutlet UILabel *titleLabel;
@property (strong, nonatomic) IBOutlet UILabel *mainTextLabel;
@property (strong, nonatomic) IBOutlet UIButton *callToActionButton;
@property (strong, nonatomic) IBOutlet UIImageView *iconImageView;
@property (strong, nonatomic) IBOutlet UIImageView *mainImageView;

MoPubAdTableViewCell.m

@synthesize titleLabel, mainImageView, iconImageView, mainTextLabel,callToActionButton;
- (void)awakeFromNib
{
    // Initialization code
}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
    [super setSelected:selected animated:animated];
    // Configure the view for the selected state
}

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self)
    {
        UIView *viewBackground = [[UIView alloc]init];
        [viewBackground.layer setMasksToBounds:YES];
        [viewBackground.layer setBorderWidth:1.0];
        [viewBackground.layer setBorderColor:[[UIColor colorWithRed:165.0/255.0 green:166.0/255.0 blue:167.0/255.0 alpha:1.0]CGColor]];
        [viewBackground setBackgroundColor:[UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:0.2]];
        if (iPhone4)
        {
            viewBackground.frame = CGRectMake(4, 4, [UIScreen mainScreen].bounds.size.width - 8 , 305 - 8 + 5);
        }
        else if (iPhone5)
        {
            viewBackground.frame = CGRectMake(4, 4, [UIScreen mainScreen].bounds.size.width - 8 , 305 - 8 + 5);
        }
        else if (iPhone6)
        {
            viewBackground.frame = CGRectMake(4, 4, [UIScreen mainScreen].bounds.size.width - 8 , 330 - 8 + 8);
        }
        else if (iPhone6Plus)
        {
            viewBackground.frame = CGRectMake(4, 4, [UIScreen mainScreen].bounds.size.width - 8 , 355 - 8 );
        }
        [self addSubview:viewBackground];
        self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 48, 48)];
//        self.iconImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 10, 60, 60)];
        [self.iconImageView.layer setMasksToBounds:YES];
        self.iconImageView.layer.cornerRadius = 5.0;
        [self addSubview:self.iconImageView];

        self.titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(70, 10, 252, 25)];
        [self.titleLabel setFont:[UIFont fontWithName:@"Roboto-Bold" size:15.0]];
        [self.titleLabel setText:@"Title"];
        [self.titleLabel setBackgroundColor:[UIColor clearColor]];
        [self.titleLabel setAdjustsFontSizeToFitWidth:YES];
        [self addSubview:self.titleLabel];

        UILabel *lblSponsored = [[UILabel alloc]initWithFrame:CGRectMake(self.titleLabel.frame.origin.x, self.titleLabel.frame.origin.y + self.titleLabel.frame.size.height , self.titleLabel.frame.size.width, 20)];
        lblSponsored.text = @"Sponsored";
        lblSponsored.font = [UIFont fontWithName:@"Roboto-Regular" size:13.0];
        [lblSponsored setTextAlignment:NSTextAlignmentLeft];
        lblSponsored.textColor = [UIColor lightGrayColor];
        [lblSponsored setBackgroundColor:[UIColor clearColor]];
        [self addSubview:lblSponsored];

        self.mainTextLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 65, [UIScreen mainScreen].bounds.size.width - 20, 35)];
        [self.mainTextLabel setFont:[UIFont systemFontOfSize:14.0f]];
        [self.mainTextLabel setBackgroundColor:[UIColor clearColor]];
        [self.mainTextLabel setText:@"Text"];
        [self.mainTextLabel setNumberOfLines:2];
        [self addSubview:self.mainTextLabel];

        self.mainImageView.contentMode = UIViewContentModeScaleAspectFit;

        if (iPhone4 || iPhone5)
        {
            self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, self.mainTextLabel.frame.size.height + self.mainTextLabel.frame.origin.y + 5, [UIScreen mainScreen].bounds.size.width - 20, 157)]; // 268
        }
        else if (iPhone6)
        {
            self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, self.mainTextLabel.frame.size.height + self.mainTextLabel.frame.origin.y + 5, [UIScreen mainScreen].bounds.size.width - 20, 185)]; //320 260
        }
        else if (iPhone6Plus)
        {
            self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, self.mainTextLabel.frame.size.height + self.mainTextLabel.frame.origin.y + 5, [UIScreen mainScreen].bounds.size.width - 20, 205)]; // 368
        }
        else
        {
            self.mainImageView = [[UIImageView alloc] initWithFrame:CGRectMake(10, self.mainTextLabel.frame.size.height + self.mainTextLabel.frame.origin.y + 5, [UIScreen mainScreen].bounds.size.width - 20, 157)];
        }
        [self.mainImageView setClipsToBounds:YES];
//        [self.mainImageView setContentMode:UIViewContentModeScaleAspectFill];
        [self addSubview:self.mainImageView];

        self.callToActionButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [self.callToActionButton setFrame:CGRectMake([UIScreen mainScreen].bounds.size.width - 105, self.mainImageView.frame.origin.y + self.mainImageView.frame.size.height + 5, 95, 30)];
        [self.callToActionButton.titleLabel setAdjustsFontSizeToFitWidth:YES];
        [self.callToActionButton.layer setMasksToBounds:YES];
//        [self.callToActionButton.layer setBorderWidth:1.0];
        [self.callToActionButton.layer setCornerRadius:5.0];
//        [self.callToActionButton.layer setBorderColor:[[UIColor colorWithRed:165.0/255.0 green:166.0/255.0 blue:167.0/255.0 alpha:1.0]CGColor]];
        [self.callToActionButton setBackgroundColor:[UIColor colorWithRed:0.0/255.0 green:158.0/255.0 blue:255.0/255.0 alpha:1.0]];
        self.callToActionButton.userInteractionEnabled = NO;
        [self addSubview:self.callToActionButton];

        self.backgroundColor = [UIColor colorWithWhite:0 alpha:0.5f];
        self.titleLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f];
        self.mainTextLabel.textColor = [UIColor colorWithWhite:0.86 alpha:1.0f];
        self.titleLabel.font = [UIFont fontWithName:@"Roboto-Bold" size:15.0];
        self.mainTextLabel.font = [UIFont fontWithName:@"Roboto-Regular" size:13.0];

    }
    return self;
}

- (void)layoutAdAssets:(MPNativeAd *)adObject
{
    [adObject loadTitleIntoLabel:self.titleLabel];
    [adObject loadTextIntoLabel:self.mainTextLabel];
    [adObject loadCallToActionTextIntoButton:self.callToActionButton];
//    [adObject loadCallToActionTextIntoLabel:self.callToActionButton.titleLabel];
    [adObject loadIconIntoImageView:self.iconImageView];
    [adObject loadImageIntoImageView:self.mainImageView];
}

+ (CGSize)sizeWithMaximumWidth:(CGFloat)maximumWidth
{
    if (iPhone4 || iPhone5)
    {
        return CGSizeMake(maximumWidth, 305 + 5);
    }
    else if (iPhone6)
    {
        return CGSizeMake(maximumWidth, 330 + 8);
    }
    else
    {
        return CGSizeMake(maximumWidth, 355 );
    }
    return CGSizeMake(maximumWidth, 295 + 15);
}

I hope this will be helpful to you.

Note: Whichever tableview method you are using, use the methods with mp_ prefix in your ViewController.

Vatsal K
  • 1,031
  • 10
  • 31
  • yes this is very helpful, But I still see that you are manually setting the frames of the ImageViews. If you get an image larger, would it automatically increase the imageView frame for you? or just scale it proportionally? In other words, will the imageview always be the same size? – Jacky Wang May 05 '16 at 06:48
  • 1
    btw, which version of the SDK are you using? How come in my MPNativeAdRendering protocol, I only have the function - (void)layoutCustomAssetsWithProperties:(NSDictionary *)customProperties imageLoader:(MPNativeAdRenderingImageLoader*)imageLoader; instead of the one you are using; - (void)layoutAdAssets:(MPNativeAd *)adObject; – Jacky Wang May 05 '16 at 06:50
  • As I said, image you are getting from advertisements would always be same with high resolution. so that you don't need to bother regarding image. Because advertisement images are generalized and will be used by ad network. – Vatsal K May 05 '16 at 06:50
  • I have implemented using 3.13.0 version. And I guess current version is 4.6.0. – Vatsal K May 05 '16 at 07:08
4

After contacting Twitter tech support, I confirmed that currently iOS does not support dynamic cell height. The only way is to size your views according to the best practice as @Vatsal K pointed out in his comments above.

enter image description here

Jacky Wang
  • 618
  • 7
  • 27
  • This is unfortunate. Even with fixed image aspect ratios, the dynamic lengths of text provided by advertisers necessitates sizing the view to accommodate 1, 2, or more lines, especially when many networks don't respect MoPub's length guidelines. I'm currently hunting for a workaround. – kball Jun 27 '16 at 20:51
  • See my answer below – kball Jun 27 '16 at 22:04
2

Update: While the method below works for the stock MoPub Marketplace adapters, I've found a bug in MoPub's SDK that can cause issues with other adapters. Specifically, Facebook's privacy icon view won't appear if you use this method. retrieveAdViewForSizeCalculationWithError calls retrieveViewWithAdapter. This instantiates a new adview for sizing purposes (so this method is also probably not very efficient). The problem arises in that method when it adds a custom network adapters privacy view as a subview. Because MoPub does not instantiate a new adapter, the adapter's privacy view is added as a subview to this template adview and removed from the real adview. Tread carefully.


I've found a workaround but it relies on a constant not exposed by the MoPub SDK interface.

If you look at MPStreamAdPlacer

- (CGSize)sizeForAd:(MPNativeAd *)ad withMaximumWidth:(CGFloat)maxWidth andIndexPath:(NSIndexPath *)indexPath
{
    id<MPNativeAdRenderer> renderer = ad.renderer;

    CGSize adSize;

    if ([renderer respondsToSelector:@selector(viewSizeHandler)] && renderer.viewSizeHandler) {
        adSize = [renderer viewSizeHandler](maxWidth);
        if (adSize.height == MPNativeViewDynamicDimension) {
            UIView *adView = [ad retrieveAdViewForSizeCalculationWithError:nil];
            if (adView) {
                CGSize hydratedAdViewSize = [adView sizeThatFits:CGSizeMake(adSize.width, CGFLOAT_MAX)];
                return hydratedAdViewSize;
            }
        }
        return adSize;
    }

This method is called by MPTableViewAdPlacer to get the size for the ad. It in turn uses the viewSizeHandler you set when configuring your placer. Notice that if the returned height is equal to MPNativeViewDynamicDimension (-1.0) then it falls back to calling -sizeThatFits: on the ad view.

At this point the ad view has been configured with the data from the ad server so you can implement -sizeThatFits: and return a size appropriate for the content:

// MoPub setup
MPStaticNativeAdRendererSettings *settings = [[MPStaticNativeAdRendererSettings alloc] init];
settings.renderingViewClass = [MyAdView class];
settings.viewSizeHandler = ^(CGFloat maxWidth) {
    return CGSizeMake(maxWidth, -1.0);
};

MyAdView class:

- (CGSize)sizeThatFits:(CGSize)size {

    // Inspect the configured labels and images to determine appropriate height for given size.width
    CGFloat height = ...

    return CGSizeMake(size.width, height);
}

Warning

MPNativeViewDynamicDimension is internal MPStaticNativeAdRenderer and so the value and behavior could potentially change without notice. Might be worth adding a check of the linked SDK version and if it changes issue a warning to check that the implementation is still valid

kball
  • 4,923
  • 3
  • 29
  • 31