[ios] New way of banners showing.

This commit is contained in:
VladiMihaylenko 2017-03-10 14:54:01 +03:00 committed by Sergey Yershov
parent 9ab22f3d3c
commit 25867c3dff
9 changed files with 168 additions and 83 deletions

View file

@ -152,7 +152,7 @@ static NSString * const kStatOther = @"Other";
static NSString * const kStatOut = @"Out";
static NSString * const kStatPedestrian = @"Pedestrian";
static NSString * const kStatPlacePage = @"Place page";
static NSString * const kStatPlacePageBannerClick = @"Placepage_Banner_click";
static NSString * const kStatPlacePageBannerError = @"Placepage_Banner_error";
static NSString * const kStatPlacePageBannerShow = @"Placepage_Banner_show";
static NSString * const kPlacePageHotelBook = @"Placepage_Hotel_book";
static NSString * const kPlacePageHotelDetails = @"Placepage_Hotel_details";

View file

@ -19,70 +19,37 @@ enum FBAdsBannerState: Int {
@objc(MWMFBAdsBanner)
final class FBAdsBanner: UITableViewCell {
@IBOutlet private var detailedModeConstraints: [NSLayoutConstraint]!
@IBOutlet fileprivate weak var adIconImageView: UIImageView!
@IBOutlet fileprivate weak var adTitleLabel: UILabel!
@IBOutlet fileprivate weak var adBodyLabel: UILabel!
@IBOutlet fileprivate var adCallToActionButtonCompact: UIButton!
@IBOutlet fileprivate var adCallToActionButtonDetailed: UIButton!
@IBOutlet private weak var adIconImageView: UIImageView!
@IBOutlet private weak var adTitleLabel: UILabel!
@IBOutlet private weak var adBodyLabel: UILabel!
@IBOutlet private var adCallToActionButtonCompact: UIButton!
@IBOutlet private var adCallToActionButtonDetailed: UIButton!
static let detailedBannerExcessHeight: Float = 36
var state = FBAdsBannerState.compact {
var state = alternative(iPhone: FBAdsBannerState.compact, iPad: FBAdsBannerState.detailed) {
didSet {
let config = state.config()
layoutIfNeeded()
adBodyLabel.numberOfLines = config.numberOfLines
detailedModeConstraints.forEach { $0.priority = config.priority }
setNeedsLayout()
UIView.animate(withDuration: kDefaultAnimationDuration) { self.layoutIfNeeded() }
}
}
fileprivate var nativeAd: FBNativeAd?
private var nativeAd: FBNativeAd?
func config(placementID: String) {
style()
contentView.alpha = 0
state = .compact
let nativeAd = FBNativeAd(placementID: placementID)
nativeAd.delegate = self
nativeAd.mediaCachePolicy = .all
nativeAd.load()
}
private func style() {
adTitleLabel.font = UIFont.bold12()
adBodyLabel.font = UIFont.regular12()
adCallToActionButtonCompact.titleLabel?.font = UIFont.regular12()
adCallToActionButtonDetailed.titleLabel?.font = UIFont.regular15()
backgroundColor = UIColor.bannerBackground()
let color = UIColor.blackSecondaryText()!
adTitleLabel.textColor = color
adBodyLabel.textColor = color
adCallToActionButtonCompact.setTitleColor(color, for: .normal)
adCallToActionButtonDetailed.setTitleColor(color, for: .normal)
adCallToActionButtonDetailed.backgroundColor = UIColor.bannerButtonBackground()
let layer = adCallToActionButtonCompact.layer
layer.borderColor = color.cgColor
layer.borderWidth = 1
layer.cornerRadius = 4
}
}
extension FBAdsBanner: FBNativeAdDelegate {
func nativeAdDidLoad(_ nativeAd: FBNativeAd) {
if let sNativeAd = self.nativeAd {
sNativeAd.unregisterView()
}
self.nativeAd = nativeAd
func config(ad: FBNativeAd) {
nativeAd = ad
ad.unregisterView()
let adCallToActionButtons = [adCallToActionButtonCompact!, adCallToActionButtonDetailed!]
nativeAd.registerView(forInteraction: self, with: nil, withClickableViews: adCallToActionButtons)
ad.registerView(forInteraction: self, with: nil, withClickableViews: adCallToActionButtons)
nativeAd.icon?.loadAsync { [weak self] image in
ad.icon?.loadAsync { [weak self] image in
self?.adIconImageView.image = image
}
adTitleLabel.text = nativeAd.title
adBodyLabel.text = nativeAd.body
adCallToActionButtons.forEach { $0.setTitle(nativeAd.callToAction, for: .normal) }
UIView.animate(withDuration: kDefaultAnimationDuration) { self.contentView.alpha = 1 }
adTitleLabel.text = ad.title
adBodyLabel.text = ad.body
adCallToActionButtons.forEach { $0.setTitle(ad.callToAction, for: .normal) }
}
}

View file

@ -51,12 +51,20 @@
<fontDescription key="fontDescription" type="system" weight="semibold" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="bold12"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="551" verticalHuggingPriority="249" horizontalCompressionResistancePriority="249" text="Как превратить обычный день в праздник? Просто сделай заказ через Delivery Club!" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Ev3-yY-ql1">
<rect key="frame" x="16" y="27" width="258.5" height="74.5"/>
<fontDescription key="fontDescription" type="system" pointSize="12"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular12"/>
<userDefinedRuntimeAttribute type="string" keyPath="colorName" value="blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</label>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="551" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9qA-JC-fkn">
<rect key="frame" x="290" y="18" width="69" height="24"/>
@ -70,6 +78,19 @@
<state key="normal" title="Заказать">
<color key="titleColor" red="0.59999999999999998" green="0.58823529411764708" blue="0.56862745098039214" alpha="0.40000000000000002" colorSpace="calibratedRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular12"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
<userDefinedRuntimeAttribute type="color" keyPath="layer.borderUIColor">
<color key="value" red="0.0" green="0.0" blue="0.0" alpha="0.59999999999999998" colorSpace="calibratedRGB"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="layer.borderWidth">
<integer key="value" value="1"/>
</userDefinedRuntimeAttribute>
<userDefinedRuntimeAttribute type="number" keyPath="layer.cornerRadius">
<integer key="value" value="4"/>
</userDefinedRuntimeAttribute>
</userDefinedRuntimeAttributes>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="NKM-3R-3g1">
<rect key="frame" x="0.0" y="109.5" width="375" height="36"/>
@ -80,6 +101,11 @@
<state key="normal" title="Заказать">
<color key="titleColor" red="0.0" green="0.0" blue="0.0" alpha="0.40000000000000002" colorSpace="calibratedRGB"/>
</state>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="fontName" value="regular15"/>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="bannerButtonBackground"/>
<userDefinedRuntimeAttribute type="string" keyPath="textColorName" value="blackSecondaryText"/>
</userDefinedRuntimeAttributes>
</button>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
@ -107,6 +133,9 @@
<constraint firstItem="zWu-Gh-Vf7" firstAttribute="leading" secondItem="f76-qn-ne4" secondAttribute="leading" priority="600" constant="16" id="vlX-zx-nfP"/>
<constraint firstItem="zWu-Gh-Vf7" firstAttribute="leading" secondItem="EuF-Rm-DHQ" secondAttribute="trailing" priority="250" constant="8" id="w5K-BT-1ED"/>
</constraints>
<userDefinedRuntimeAttributes>
<userDefinedRuntimeAttribute type="string" keyPath="backgroundColorName" value="bannerBackground"/>
</userDefinedRuntimeAttributes>
</tableViewCellContentView>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<connections>

View file

@ -88,17 +88,20 @@ enum class OpeningHours
Unknown
};
using NewSectionsReady = void(^)(NSRange const & range);
using NewSectionsAreReady = void (^)(NSRange const & range);
using BannerIsReady = void (^)();
} // namespace place_page
@class MWMGalleryItemModel;
@class FBNativeAd;
/// ViewModel for place page.
@interface MWMPlacePageData : NSObject
@property (copy, nonatomic) place_page::NewSectionsReady sectionsReadyCallback;
@property(copy, nonatomic) place_page::NewSectionsAreReady sectionsAreReadyCallback;
@property(copy, nonatomic) place_page::BannerIsReady bannerIsReadyCallback;
// ready callback will be called from main queue.
- (instancetype)initWithPlacePageInfo:(place_page::Info const &)info;
@ -131,7 +134,7 @@ using NewSectionsReady = void(^)(NSRange const & range);
- (NSArray<MWMGalleryItemModel *> *)photos;
// Banner
- (banners::Banner)banner;
- (FBNativeAd *)nativeAd;
// API
- (NSString *)apiURL;

View file

@ -2,8 +2,11 @@
#import "AppInfo.h"
#import "MWMNetworkPolicy.h"
#import "MWMSettings.h"
#import "Statistics.h"
#import "SwiftBridge.h"
#import <FBAudienceNetwork/FBAudienceNetwork.h>
#include "Framework.h"
#include "indexer/banners.hpp"
@ -20,9 +23,10 @@ NSString * const kUserDefaultsLatLonAsDMSKey = @"UserDefaultsLatLonAsDMS";
using namespace place_page;
@interface MWMPlacePageData()
@interface MWMPlacePageData ()<FBNativeAdDelegate>
@property(copy, nonatomic) NSString * cachedMinPrice;
@property(nonatomic) FBNativeAd * nativeAd;
@end
@ -97,7 +101,13 @@ using namespace place_page;
NSAssert(!m_previewRows.empty(), @"Preview row's can't be empty!");
m_previewRows.push_back(PreviewRows::Space);
if (network_policy::CanUseNetwork() && ![MWMSettings adForbidden] && m_info.HasBanner())
m_previewRows.push_back(PreviewRows::Banner);
{
self.nativeAd =
[[FBNativeAd alloc] initWithPlacementID:@(m_info.GetBanner().m_bannerId.c_str())];
self.nativeAd.delegate = self;
self.nativeAd.mediaCachePolicy = FBNativeAdsCachePolicyAll;
[self.nativeAd loadAd];
}
}
- (void)fillMetaInfoSection
@ -237,8 +247,8 @@ using namespace place_page;
reviewsRows.emplace_back(HotelReviewsRow::ShowMore);
length++;
}
self.sectionsReadyCallback({position, length});
self.sectionsAreReadyCallback({position, length});
});
});
}
@ -407,10 +417,6 @@ using namespace place_page;
return res;
}
#pragma mark - Banner
- (banners::Banner)banner { return m_info.GetBanner(); }
#pragma mark - Bookmark
- (NSString *)externalTitle
@ -510,4 +516,34 @@ using namespace place_page;
return [result componentsJoinedByString:@", "];
}
#pragma mark - FBNativeAdDelegate
- (void)nativeAdDidLoad:(FBNativeAd *)nativeAd
{
if (![nativeAd isEqual:self.nativeAd])
return;
[Statistics logEvent:kStatPlacePageBannerShow
withParameters:@{
kStatTags : self.statisticsTags,
kStatBanner : @(m_info.GetBanner().m_bannerId.c_str()),
kStatProvider : kStatFacebook
}];
dispatch_async(dispatch_get_main_queue(), ^{
self->m_previewRows.push_back(PreviewRows::Banner);
self.bannerIsReadyCallback();
});
}
- (void)nativeAd:(FBNativeAd *)nativeAd didFailWithError:(NSError *)error
{
[Statistics logEvent:kStatPlacePageBannerError
withParameters:@{
kStatTags : self.statisticsTags,
kStatBanner : @(m_info.GetBanner().m_bannerId.c_str()),
kStatProvider : kStatFacebook
}];
}
@end

View file

@ -118,12 +118,14 @@ map<MetainfoRows, Class> const kMetaInfoCells = {
{
self.isPlacePageButtonsEnabled = YES;
self.data = data;
data.sectionsReadyCallback = ^(NSRange const & range)
{
data.sectionsAreReadyCallback = ^(NSRange const & range) {
[self.placePageView.tableView insertSections:[NSIndexSet indexSetWithIndexesInRange:range]
withRowAnimation:UITableViewRowAnimationAutomatic];
};
data.bannerIsReadyCallback = ^{
[self.previewLayoutHelper insertRowAtTheEnd];
};
self.bookmarkCell = nil;
[self.actionBar configureWithData:static_cast<id<MWMActionBarSharedData>>(data)];

View file

@ -23,7 +23,8 @@ CGFloat const kLuftDraggingOffset = 30;
CGFloat const kMinOffset = 1;
} // namespace
@interface MWMiPhonePlacePageLayoutImpl () <UIScrollViewDelegate, UITableViewDelegate>
@interface MWMiPhonePlacePageLayoutImpl ()<UIScrollViewDelegate, UITableViewDelegate,
MWMPPPreviewLayoutHelperDelegate>
@property(nonatomic) MWMPPScrollView * scrollView;
@property(nonatomic) ScrollDirection direction;
@ -118,6 +119,29 @@ CGFloat const kMinOffset = 1;
self.scrollView.contentSize = {size.width, size.height + self.placePageView.height};
}
- (void)setPreviewLayoutHelper:(MWMPPPreviewLayoutHelper *)previewLayoutHelper
{
previewLayoutHelper.delegate = self;
_previewLayoutHelper = previewLayoutHelper;
}
#pragma mark - MWMPPPreviewLayoutHelperDelegate
- (void)heightWasChanged
{
auto scrollView = self.scrollView;
scrollView.scrollEnabled = NO;
dispatch_async(dispatch_get_main_queue(), ^{
auto actionBar = self.actionBar;
actionBar.maxY = actionBar.superview.height;
self.expandedContentOffset =
self.previewLayoutHelper.height + actionBar.height - self.placePageView.top.height;
if (self.state == State::Bottom)
[scrollView setContentOffset:{ 0, self.expandedContentOffset } animated:YES];
scrollView.scrollEnabled = YES;
});
}
#pragma mark - UIScrollViewDelegate
- (BOOL)isPortrait

View file

@ -1,13 +1,21 @@
@class MWMPlacePageData;
@protocol MWMPPPreviewLayoutHelperDelegate<NSObject>
- (void)heightWasChanged;
@end
@interface MWMPPPreviewLayoutHelper : NSObject
- (instancetype)initWithTableView:(UITableView *)tableView;
- (void)configWithData:(MWMPlacePageData *)data;
- (void)setDelegate:(id<MWMPPPreviewLayoutHelperDelegate>)delegate;
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
withData:(MWMPlacePageData *)data;
- (void)rotateDirectionArrowToAngle:(CGFloat)angle;
- (void)setDistanceToObject:(NSString *)distance;
- (void)insertRowAtTheEnd;
- (CGFloat)height;
- (void)layoutInOpenState:(BOOL)isOpen;

View file

@ -134,6 +134,9 @@ array<Class, 8> const kPreviewCells = {{[_MWMPPPTitle class], [_MWMPPPExternalTi
@property(weak, nonatomic) NSLayoutConstraint * distanceCellTrailing;
@property(weak, nonatomic) UIView * distanceView;
@property(weak, nonatomic) MWMPlacePageData * data;
@property(weak, nonatomic) id<MWMPPPreviewLayoutHelperDelegate> delegate;
@property(nonatomic) CGFloat leading;
@property(nonatomic) MWMDirectionView * directionView;
@property(copy, nonatomic) NSString * distance;
@ -168,9 +171,10 @@ array<Class, 8> const kPreviewCells = {{[_MWMPPPTitle class], [_MWMPPPExternalTi
- (void)configWithData:(MWMPlacePageData *)data
{
self.data = data;
auto const & previewRows = data.previewRows;
using place_page::PreviewRows;
self.lastCellIsBanner = previewRows.back() == PreviewRows::Banner;
self.lastCellIsBanner = NO;
self.lastCellIndexPath = [NSIndexPath indexPathForRow:previewRows.size() - 1 inSection:0];
auto it = find(previewRows.begin(), previewRows.end(), PreviewRows::Space);
if (it != previewRows.end())
@ -230,21 +234,8 @@ array<Class, 8> const kPreviewCells = {{[_MWMPPPTitle class], [_MWMPPPExternalTi
case PreviewRows::Space:
return c;
case PreviewRows::Banner:
auto banner = [data banner];
NSString * bannerId = @(banner.m_bannerId.c_str());
[Statistics logEvent:kStatPlacePageBannerShow
withParameters:@{
kStatTags : data.statisticsTags,
kStatBanner : bannerId,
kStatState : IPAD ? @1 : @0
}];
auto bannerCell = static_cast<MWMFBAdsBanner *>(c);
using namespace banners;
switch (banner.m_type)
{
case Banner::Type::None: NSAssert(false, @"Invalid banner type"); break;
case Banner::Type::Facebook: [bannerCell configWithPlacementID:bannerId]; break;
}
[bannerCell configWithAd:data.nativeAd];
self.cachedBannerCell = bannerCell;
return bannerCell;
}
@ -302,14 +293,39 @@ array<Class, 8> const kPreviewCells = {{[_MWMPPPTitle class], [_MWMPPPExternalTi
self.directionView.distanceLabel.text = distance;
}
- (void)insertRowAtTheEnd
{
auto const & previewRows = self.data.previewRows;
auto const size = previewRows.size();
self.lastCellIsBanner = previewRows.back() == place_page::PreviewRows::Banner;
self.lastCellIndexPath =
[NSIndexPath indexPathForRow:size - 1
inSection:static_cast<NSUInteger>(place_page::Sections::Preview)];
[self.tableView insertRowsAtIndexPaths:@[ self.lastCellIndexPath ]
withRowAnimation:UITableViewRowAnimationLeft];
[self.delegate heightWasChanged];
}
- (CGFloat)height
{
auto const rect = [self.tableView rectForRowAtIndexPath:self.lastCellIndexPath];
return rect.origin.y + rect.size.height + (self.lastCellIsBanner ? 4 : 0);
auto const height = rect.origin.y + rect.size.height;
if (!self.lastCellIndexPath)
return height;
auto constexpr gapBannerHeight = 4.0;
CGFloat const excessHeight = self.cachedBannerCell.state == MWMFBAdsBannerStateDetailed
? [MWMFBAdsBanner detailedBannerExcessHeight]
: 0;
return height + gapBannerHeight - excessHeight;
}
- (void)layoutInOpenState:(BOOL)isOpen
{
if (IPAD)
return;
[self.tableView update:^{
self.cachedBannerCell.state = isOpen ? MWMFBAdsBannerStateDetailed : MWMFBAdsBannerStateCompact;
}];