Skip to content

Commit

Permalink
Added placeholder support (#4897)
Browse files Browse the repository at this point in the history
* Added placeholder suport

* Updated per CR comments
  • Loading branch information
jwoo-msft committed Oct 22, 2020
1 parent 50741fc commit d66028e
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,31 @@ @implementation ADCResolver
- (UIImageView *)resolveImageViewResource:(NSURL *)url
{
__block UIImageView *imageView = [[UIImageView alloc] init];
NSURLSessionDownloadTask *downloadPhotoTask = [[NSURLSession sharedSession]
downloadTaskWithURL:url
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// iOS uses NSInteger as HTTP URL status
NSInteger status = 200;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
status = ((NSHTTPURLResponse *)response).statusCode;
}
if (!error && status == 200) {
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
// check if custom scheme bundle exists
if ([url.scheme isEqualToString:@"bundle"]) {
// if bundle scheme, load an image from sample's main bundle
UIImage *image = [UIImage imageNamed:url.pathComponents.lastObject];
imageView.image = image;
} else {
NSURLSessionDownloadTask *downloadPhotoTask = [[NSURLSession sharedSession]
downloadTaskWithURL:url
completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// iOS uses NSInteger as HTTP URL status
NSInteger status = 200;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
status = ((NSHTTPURLResponse *)response).statusCode;
}
}
}];
[downloadPhotoTask resume];
if (!error && status == 200) {
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = image;
});
}
}
}];
[downloadPhotoTask resume];
}
return imageView;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ - (void)viewDidLoad
[_resolvers setResourceResolver:resolver scheme:@"http"];
[_resolvers setResourceResolver:resolver scheme:@"https"];
[_resolvers setResourceResolver:resolver scheme:@"data"];
// register a custom scheme bundle with resolver
[_resolvers setResourceResolver:resolver scheme:@"bundle"];
_enableCustomRenderer = NO;
self.curView = nil;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,38 @@ - (UIView *)render:(UIView<ACRIContentHoldingView> *)viewGroup
BOOL isAspectRatioNeeded = !(pixelWidth && pixelHeight);
CGSize cgsize = [acoConfig getImageSize:imgElem->GetImageSize()];

NSString *key = [NSString stringWithCString:imgElem->GetUrl().c_str() encoding:[NSString defaultCStringEncoding]];
// makes parts for building a key to UIImage, there are different interfaces for loading the images
// we list all the parts that are needed in building the key.
NSString *number = [[NSNumber numberWithUnsignedLongLong:(unsigned long long)(elem.get())] stringValue];
NSString *urlString = [NSString stringWithCString:imgElem->GetUrl().c_str() encoding:[NSString defaultCStringEncoding]];
NSDictionary *pieces = @{
@"number" : number,
@"url" : urlString
};

NSString *key = makeKeyForImage(acoConfig, @"image", pieces);
NSMutableDictionary *imageViewMap = [rootView getImageMap];
NSURL *url = [NSURL URLWithString:key];
UIImage *img = imageViewMap[key];

// try get an UIImageView
view = [rootView getImageView:key];
if (!view && img) {
// if an UIImage is available, but UIImageView is missing, create one
ACRUIImageView *acrImageView = [[ACRUIImageView alloc] initWithFrame:CGRectMake(0, 0, cgsize.width, cgsize.height)];
acrImageView.image = img;
if (imgElem->GetImageStyle() == ImageStyle::Person) {
acrImageView.isPersonStyle = YES;
[acrImageView setNeedsLayout];
}
view = acrImageView;
}

if (ACOImageViewIF == [acoConfig getResolverIFType:[url scheme]]) {
NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)(elem.get())];
key = [number stringValue];
if (view && img) {
// if we already have UIImageView and UIImage, configures the constraints and turn off the notification
[rootView removeObserverOnImageView:@"image" onObject:view keyToImageView:key];
[self configUpdateForUIImageView:acoElem config:acoConfig image:img imageView:view];
}

UIImage *img = imageViewMap[key];
ImageSize size = ImageSize::None;
CGSize intrinsicContentSize;
if (!hasExplicitMeasurements) {
Expand Down Expand Up @@ -85,22 +107,6 @@ - (UIView *)render:(UIView<ACRIContentHoldingView> *)viewGroup
}
}


if (img) {
ACRUIImageView *acrImageView = [[ACRUIImageView alloc] initWithFrame:CGRectMake(0, 0, cgsize.width, cgsize.height)];
acrImageView.image = img;
if (imgElem->GetImageStyle() == ImageStyle::Person) {
acrImageView.isPersonStyle = YES;
[acrImageView setNeedsLayout];
}
view = acrImageView;
[self configUpdateForUIImageView:acoElem config:acoConfig image:img imageView:view];
} else {
NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)imgElem.get()];
NSString *key = [number stringValue];
view = [rootView getImageView:key];
}

ACRContentHoldingUIView *wrappingview = [[ACRContentHoldingUIView alloc] initWithFrame:view.frame];

if (!view) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,26 +43,41 @@ - (UIView *)render:(UIView<ACRIContentHoldingView> *)viewGroup
std::shared_ptr<Media> mediaElem = std::dynamic_pointer_cast<Media>(elem);

NSMutableDictionary *imageViewMap = [rootView getImageMap];
NSString *key = [NSString stringWithCString:mediaElem->GetPoster().c_str() encoding:[NSString defaultCStringEncoding]];
UIImage *img = imageViewMap[key];

// makes parts for building a key to UIImage, there are different interfaces for loading the images
// we list all the parts that are needed in building the key.
NSString *urlString = [NSString stringWithCString:mediaElem->GetPoster().c_str() encoding:[NSString defaultCStringEncoding]];
NSString *numberString = [[NSNumber numberWithUnsignedLongLong:(unsigned long long)(elem.get())] stringValue];
NSString *piikey = [NSString stringWithCString:[acoConfig getHostConfig] -> GetMedia().playButton.c_str() encoding:[NSString defaultCStringEncoding]];
NSString *piikeyViewIF = [NSString stringWithFormat:@"%llu_playIcon", (unsigned long long)elem.get()];

NSDictionary *pieces = @{
@"number" : numberString,
@"url" : urlString,
@"playicon-url-imageView" : piikey,
@"playicon-url-viewIF" : piikeyViewIF
};

NSString *mediaKey = makeKeyForImage(acoConfig, @"media-poster", pieces);
UIImage *img = imageViewMap[mediaKey];
UIImageView *view = nil;
CGFloat heightToWidthRatio = 0.0f;
ACRContentHoldingUIView *contentholdingview = nil;

// if poster is available, restrict the image size to the width of superview, and adjust the height accordingly
if (img) {
view = [[UIImageView alloc] initWithImage:img];

if (img.size.width > 0) {
heightToWidthRatio = img.size.height / img.size.width;
contentholdingview = (ACRContentHoldingUIView *)[rootView getImageView:mediaKey];
if (contentholdingview) {
view = contentholdingview.subviews[0];
} else {
view = [[UIImageView alloc] initWithImage:img];
contentholdingview = [[ACRContentHoldingUIView alloc] init];
[contentholdingview addSubview:view];
}
contentholdingview = [[ACRContentHoldingUIView alloc] init];
[contentholdingview addSubview:view];
// if we already have UIImageView and UIImage, configures the constraints and turn off the notification
[self configUpdateForUIImageView:acoElem config:acoConfig image:img imageView:view];
[rootView removeObserverOnImageView:@"image" onObject:view keyToImageView:mediaKey];
} else {
NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)mediaElem.get()];
NSString *key = [number stringValue];
contentholdingview = (ACRContentHoldingUIView *)[rootView getImageView:key];
contentholdingview = (ACRContentHoldingUIView *)[rootView getImageView:mediaKey];
if (contentholdingview) {
view = contentholdingview.subviews[0];
}
Expand All @@ -81,16 +96,16 @@ - (UIView *)render:(UIView<ACRIContentHoldingView> *)viewGroup
view.contentMode = UIViewContentModeScaleAspectFill;
contentholdingview.isMediaType = YES;


// process play icon image
NSString *piikey = [NSString stringWithCString:[acoConfig getHostConfig] -> GetMedia().playButton.c_str() encoding:[NSString defaultCStringEncoding]];
UIImage *playIconImage = imageViewMap[piikey];
NSString *playIconKey = makeKeyForImage(acoConfig, @"media-playicon-image", pieces);
UIImage *playIconImage = imageViewMap[playIconKey];
UIImageView *playIconImageView = nil;
BOOL hideDefaultPlayIcon = NO;

if (!playIconImage) {
NSString *key = [NSString stringWithFormat:@"%llu_playIcon", (unsigned long long)elem.get()];
playIconImageView = [rootView getImageView:key];
} else {
playIconImageView = [rootView getImageView:playIconKey];

if (playIconImage && !playIconImageView) {
playIconImageView = [[UIImageView alloc] initWithImage:playIconImage];
}

Expand Down
37 changes: 33 additions & 4 deletions source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ - (void)processBaseCardElement:(std::shared_ptr<BaseCardElement> const &)elem
^(NSObject<ACOIResourceResolver> *imageResourceResolver, NSString *key, std::shared_ptr<BaseCardElement> const &elem, NSURL *url, ACRView *rootView) {
UIImageView *view = [imageResourceResolver resolveImageViewResource:url];
if (view) {
// check image already exists in the returned image view and register the image
[self registerImageFromUIImageView:view key:key];
[view addObserver:self
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand All @@ -240,6 +242,8 @@ - (void)processBaseCardElement:(std::shared_ptr<BaseCardElement> const &)elem
^(NSObject<ACOIResourceResolver> *imageResourceResolver, NSString *key, std::shared_ptr<BaseCardElement> const &elem, NSURL *url, ACRView *rootView) {
UIImageView *view = [imageResourceResolver resolveImageViewResource:url];
if (view) {
// check image already exists in the returned image view and register the image
[self registerImageFromUIImageView:view key:key];
[view addObserver:self
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand Down Expand Up @@ -268,6 +272,8 @@ - (void)processBaseCardElement:(std::shared_ptr<BaseCardElement> const &)elem
UIImageView *view = [imageResourceResolver resolveImageViewResource:url];
ACRContentHoldingUIView *contentholdingview = [[ACRContentHoldingUIView alloc] initWithFrame:view.frame];
if (view) {
// check image already exists in the returned image view and register the image
[self registerImageFromUIImageView:view key:key];
[contentholdingview addSubview:view];
contentholdingview.isMediaType = YES;
[view addObserver:self
Expand All @@ -288,6 +294,8 @@ - (void)processBaseCardElement:(std::shared_ptr<BaseCardElement> const &)elem
^(NSObject<ACOIResourceResolver> *imageResourceResolver, NSString *key, std::shared_ptr<BaseCardElement> const &elem, NSURL *url, ACRView *rootView) {
UIImageView *view = [imageResourceResolver resolveImageViewResource:url];
if (view) {
// check image already exists in the returned image view and register the image
[self registerImageFromUIImageView:view key:key];
[view addObserver:rootView
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand Down Expand Up @@ -575,12 +583,25 @@ - (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDi
}
}

// remove observer from UIImageView
- (void)removeObserverOnImageView:(NSString *)KeyPath onObject:(NSObject *)object keyToImageView:(NSString *)key
{
if ([object isKindOfClass:[UIImageView class]]) {
if (_imageViewContextMap[key]) {
[self removeObserver:self forKeyPath:KeyPath onObject:object];
}
}
}

- (void)removeObserver:(NSObject *)observer forKeyPath:(NSString *)path onObject:(NSObject *)object
{
_numberOfSubscribers--;
[object removeObserver:self forKeyPath:path];
[_setOfRemovedObservers addObject:object];
[self callDidLoadElementsIfNeeded];
// check that makes sure that there are subscribers, and the given observer is not one of the removed observers
if (_numberOfSubscribers && ![_setOfRemovedObservers containsObject:object]) {
_numberOfSubscribers--;
[object removeObserver:self forKeyPath:path];
[_setOfRemovedObservers addObject:object];
[self callDidLoadElementsIfNeeded];
}
}

- (void)loadBackgroundImageAccordingToResourceResolverIF:(std::shared_ptr<BackgroundImage> const &)backgroundImage key:(NSString *)key observerAction:(ObserverActionBlock)observerAction
Expand Down Expand Up @@ -785,4 +806,12 @@ - (ACRColumnView *)peekCurrentShowCard
return showcard;
}

// check if UIImageView already contains an UIImage, if so, add it the image map.
- (void)registerImageFromUIImageView:(UIImageView *)imageView key:(NSString *)key
{
if (imageView.image) {
self->_imageViewMap[key] = imageView.image;
}
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ typedef void (^ObserverActionBlockForBaseAction)(NSObject<ACOIResourceResolver>
key:(NSString *)key
observerAction:(ObserverActionBlock)observerAction;

- (void)removeObserverOnImageView:(NSString *)KeyPath onObject:(NSObject *)object keyToImageView:(NSString *)key;

- (void)updatePaddingMap:(std::shared_ptr<CollectionTypeElement> const &)collection view:(UIView *)view;

- (UIView *)getBleedTarget:(InternalId const &)internalId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,5 @@ void configVerticalAlignmentConstraintsForBackgroundImageView(const BackgroundIm
void configWidthAndHeightAnchors(UIView *superView, UIImageView *imageView, bool isComplimentaryAxisHorizontal);

NSMutableAttributedString *initAttributedText(ACOHostConfig *acoConfig, const std::string &text, const AdaptiveCards::RichTextElementProperties &textElementProperties, ACRContainerStyle style);

NSString *makeKeyForImage(ACOHostConfig *acoConfig, NSString *keyType, NSDictionary<NSString *, NSString *> *pieces);
23 changes: 23 additions & 0 deletions source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/UtiliOS.mm
Original file line number Diff line number Diff line change
Expand Up @@ -751,3 +751,26 @@ void configWidthAndHeightAnchors(UIView *superView, UIImageView *imageView, bool

return [[NSMutableAttributedString alloc] initWithString:[NSString stringWithCString:text.c_str() encoding:NSUTF8StringEncoding] attributes:@{NSFontAttributeName : font, NSForegroundColorAttributeName : foregroundColor}];
}

NSString *makeKeyForImage(ACOHostConfig *acoConfig, NSString *keyType, NSDictionary<NSString *, NSString *> *pieces)
{
ACOResolverIFType resolverType = ACODefaultIF;
NSString *urlString = pieces[@"url"], *key = urlString;
NSURL *url = nil;

if (urlString) {
url = [NSURL URLWithString:urlString];
resolverType = [acoConfig getResolverIFType:[url scheme]];
}

if ([keyType isEqualToString:@"image"] || [keyType isEqualToString:@"media-poster"]) {
if (ACOImageViewIF == resolverType) {
key = pieces[@"number"];
}
} else if ([keyType isEqualToString:@"media-playicon-image"]) {
key = (ACOImageViewIF == resolverType) ? pieces[@"playicon-url-viewIF"] : pieces[@"playicon-url"];
} else if ([keyType isEqualToString:@"media-playicon-imageView"]) {
key = (ACOImageViewIF == resolverType) ? pieces[@"playicon-url-imageView-viewIF"] : pieces[@"playicon-url-imageView"];
}
return key;
}

0 comments on commit d66028e

Please sign in to comment.