Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added placeholder support #4897

Merged
merged 3 commits into from
Oct 22, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,29 @@ @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;
});
if ([url.scheme isEqualToString:@"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,7 @@ - (void)viewDidLoad
[_resolvers setResourceResolver:resolver scheme:@"http"];
[_resolvers setResourceResolver:resolver scheme:@"https"];
[_resolvers setResourceResolver:resolver scheme:@"data"];
[_resolvers setResourceResolver:resolver scheme:@"bundle"];
_enableCustomRenderer = NO;
self.curView = nil;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@
@protocol ACRIKVONotificationHandler

- (void)configUpdateForUIImageView:(ACOBaseCardElement *)acoElem config:(ACOHostConfig *)acoConfig image:(UIImage *)image imageView:(UIImageView *)imageView;

jwoo-msft marked this conversation as resolved.
Show resolved Hide resolved
@end
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,33 @@ - (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]];
NSString *number = [[NSNumber numberWithUnsignedLongLong:(unsigned long long)(elem.get())] stringValue];
NSString *urlString = [NSString stringWithCString:imgElem->GetUrl().c_str() encoding:[NSString defaultCStringEncoding]];
jwoo-msft marked this conversation as resolved.
Show resolved Hide resolved
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];

view = [rootView getImageView:key];
if (!view && 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;
}

if (ACOImageViewIF == [acoConfig getResolverIFType:[url scheme]]) {
NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)(elem.get())];
key = [number stringValue];
if (view && img) {
[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 @@ -84,22 +101,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,37 @@ - (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];
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];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(ACRContentHoldingUIView *)[ [](start = 29, length = 28)

Curious: Is there something like a safe casting in ObjectiveC? Do we have the right level of compiler flags enabled to catch potential issues?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Objective-C is a superset of c, but there are no other ways of casting.

if (contentholdingview) {
view = contentholdingview.subviews[0];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ews[0]; [](start = 43, length = 7)

Is there ever a chance that the subviews array is empty so that accessing the first element will result in an error/av? Should we be checking for it upfront or adding an assert condition before trying to index into it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ACRContentHoldingUIView is a wrapper view to hold a subview such as UIImageView. The content view is created when there is an image view to add to the image view map. before the image view is added to the map, the view is added to the content view.

} else {
view = [[UIImageView alloc] initWithImage:img];
contentholdingview = [[ACRContentHoldingUIView alloc] init];
[contentholdingview addSubview:view];
}
contentholdingview = [[ACRContentHoldingUIView alloc] init];
[contentholdingview addSubview:view];
[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 +92,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

media-playicon-image [](start = 57, length = 20)

Avoid hard coded literals in the code - where is this coming from? Also is this going to have potential localization impact? SHould this be out in its own const strings class for easy localizability potentially?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there will be no localization impact. the strings are only used internally.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general bad practice to hard code literals - whether they are to be localized or not.


In reply to: 509830950 [](ancestors = 509830950)

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
30 changes: 26 additions & 4 deletions source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ - (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) {
[self registerImageFromUIImageView:view key:key];
[view addObserver:self
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand All @@ -239,6 +240,7 @@ - (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) {
[self registerImageFromUIImageView:view key:key];
[view addObserver:self
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand Down Expand Up @@ -267,6 +269,7 @@ - (void)processBaseCardElement:(std::shared_ptr<BaseCardElement> const &)elem
UIImageView *view = [imageResourceResolver resolveImageViewResource:url];
ACRContentHoldingUIView *contentholdingview = [[ACRContentHoldingUIView alloc] initWithFrame:view.frame];
if (view) {
[self registerImageFromUIImageView:view key:key];
[contentholdingview addSubview:view];
contentholdingview.isMediaType = YES;
[view addObserver:self
Expand All @@ -287,6 +290,7 @@ - (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) {
[self registerImageFromUIImageView:view key:key];
[view addObserver:rootView
forKeyPath:@"image"
options:NSKeyValueObservingOptionNew
Expand Down Expand Up @@ -566,12 +570,23 @@ - (void)observeValueForKeyPath:(NSString *)path ofObject:(id)object change:(NSDi
}
}

- (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];
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 @@ -776,4 +791,11 @@ - (ACRColumnView *)peekCurrentShowCard
return showcard;
}

- (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 @@ -743,3 +743,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;
}