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 all commits
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,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]];
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];

// 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];
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];
// 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);
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
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;
}