diff --git a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj index 43a0b0fca7..c876013682 100644 --- a/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj +++ b/source/ios/AdaptiveCards/ADCIOSVisualizer/ADCIOSVisualizer.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 6B268FE520CEF89100D99C1B /* (null) in Resources */ = {isa = PBXBuildFile; }; 6B7B1A9B20C21CA900260731 /* SportingEvent.json in Resources */ = {isa = PBXBuildFile; fileRef = 6B7B1A9920C21CA800260731 /* SportingEvent.json */; }; 6B9AB30620D9857B005C8E15 /* Image.Explicit.Size.json in Resources */ = {isa = PBXBuildFile; fileRef = 6B9AB30520D9857A005C8E15 /* Image.Explicit.Size.json */; }; + 6B9AB30C20DC4FB3005C8E15 /* IconsInSomeActions.json in Resources */ = {isa = PBXBuildFile; fileRef = 6B9AB30B20DC4FB3005C8E15 /* IconsInSomeActions.json */; }; 6BF339D620A665E600DA5973 /* CustomTextBlockRenderer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6BF339D420A665E600DA5973 /* CustomTextBlockRenderer.mm */; }; 6BF339E320A66A3F00DA5973 /* AdaptiveCards.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6BF339E220A66A3F00DA5973 /* AdaptiveCards.framework */; }; 6BF339E420A66A4D00DA5973 /* AdaptiveCards.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6BF339E220A66A3F00DA5973 /* AdaptiveCards.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -140,6 +141,7 @@ 30A3885C20D315AA00AAEE59 /* NotificationCard.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = NotificationCard.json; path = ../../../../samples/Tests/NotificationCard.json; sourceTree = ""; }; 6B7B1A9920C21CA800260731 /* SportingEvent.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = SportingEvent.json; path = ../../../../samples/v1.0/Scenarios/SportingEvent.json; sourceTree = ""; }; 6B9AB30520D9857A005C8E15 /* Image.Explicit.Size.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Image.Explicit.Size.json; path = ../../../../samples/Tests/Image.Explicit.Size.json; sourceTree = ""; }; + 6B9AB30B20DC4FB3005C8E15 /* IconsInSomeActions.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = IconsInSomeActions.json; path = ../../../../samples/Tests/IconsInSomeActions.json; sourceTree = ""; }; 6BF339D420A665E600DA5973 /* CustomTextBlockRenderer.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CustomTextBlockRenderer.mm; sourceTree = ""; }; 6BF339D520A665E600DA5973 /* CustomTextBlockRenderer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomTextBlockRenderer.h; sourceTree = ""; }; 6BF339E220A66A3F00DA5973 /* AdaptiveCards.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AdaptiveCards.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -346,6 +348,7 @@ F4D33E341F045B6E00941E44 /* Jsons */ = { isa = PBXGroup; children = ( + 6B9AB30B20DC4FB3005C8E15 /* IconsInSomeActions.json */, 6B9AB30520D9857A005C8E15 /* Image.Explicit.Size.json */, 30A3885C20D315AA00AAEE59 /* NotificationCard.json */, 30860BC020C9B5C9009F9D99 /* ColumnSet_Container.VerticalStretch.json */, @@ -528,6 +531,7 @@ F4933CD41F79852C00F6EBFD /* Image.HorizontalAlignment.json in Resources */, F4933CC51F79852C00F6EBFD /* Action.Submit.json in Resources */, 6B268FE520CEF89100D99C1B /* (null) in Resources */, + 6B9AB30C20DC4FB3005C8E15 /* IconsInSomeActions.json in Resources */, F4933CED1F79852C00F6EBFD /* TextBlock.Weight.json in Resources */, F4933D061F79853B00F6EBFD /* StockUpdate.json in Resources */, 30860BC620C9B5C9009F9D99 /* ColumnSet.Input.Number.VerticalStretch.json in Resources */, diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards.xcodeproj/project.pbxproj b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards.xcodeproj/project.pbxproj index 88087f5258..3a4715374e 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards.xcodeproj/project.pbxproj +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards.xcodeproj/project.pbxproj @@ -757,6 +757,8 @@ F401A8761F0DB69B006D7AF2 /* ACRImageSetUICollectionView.mm */, F401A8791F0DCBC8006D7AF2 /* ACRImageSetRenderer.h */, F401A87A1F0DCBC8006D7AF2 /* ACRImageSetRenderer.mm */, + F42741101EF873A600399FBB /* ACRImageRenderer.h */, + F42741111EF873A600399FBB /* ACRImageRenderer.mm */, F427411E1EF9DB8000399FBB /* ACRContainerRenderer.h */, F427411F1EF9DB8000399FBB /* ACRContainerRenderer.mm */, F42741221EFB274C00399FBB /* ACRColumnRenderer.h */, @@ -766,8 +768,6 @@ F42741061EF8624F00399FBB /* ACRIBaseCardElementRenderer.h */, F42741081EF864A900399FBB /* ACRBaseCardElementRenderer.h */, F42741091EF864A900399FBB /* ACRBaseCardElementRenderer.mm */, - F42741101EF873A600399FBB /* ACRImageRenderer.h */, - F42741111EF873A600399FBB /* ACRImageRenderer.mm */, F42741141EF895AB00399FBB /* ACRTextBlockRenderer.h */, F42741151EF895AB00399FBB /* ACRTextBlockRenderer.mm */, F43A940E1F1D60E30001920B /* ACRFactSetRenderer.h */, diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.h index d00022336d..fe907acc1e 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.h @@ -11,7 +11,8 @@ @interface ACOHostConfig:NSObject @property NSArray *fontFamilyNames; - +@property BOOL allActionsHaveIcons; +@property CGFloat buttonPadding; - (instancetype)init; + (ACOHostConfigParseResult *)fromJson:(NSString *)payload; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.mm index 906f5c915e..3472edc56e 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfig.mm @@ -36,6 +36,8 @@ - (instancetype)initWithConfig:(std::shared_ptr const &)config if([UIFont.familyNames containsObject:requestedFontFamilyName]){ _fontFamilyNames = @[requestedFontFamilyName]; } + _allActionsHaveIcons = YES; + _buttonPadding = 5; } return self; } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfigPrivate.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfigPrivate.h index 6d2b9257d2..b6cc00119e 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfigPrivate.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACOHostConfigPrivate.h @@ -14,7 +14,7 @@ using namespace AdaptiveCards; @interface ACOHostConfig() - (instancetype)initWithConfig:(std::shared_ptr const &)config; -- (std::shared_ptr) getHostConfig; +- (std::shared_ptr)getHostConfig; - (void)setHostConfig:(std::shared_ptr const &)config; + (NSNumber *)getTextStrokeWidthForWeight:(TextWeight)weight; diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionOpenURLRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionOpenURLRenderer.mm index 5548f89e74..53277e988b 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionOpenURLRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionOpenURLRenderer.mm @@ -42,6 +42,8 @@ - (UIButton* )renderButton:(ACRView *)rootView [superview addTarget:target]; [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; return button; } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionShowCardRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionShowCardRenderer.mm index 4842e10b59..b5fcbc2607 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionShowCardRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionShowCardRenderer.mm @@ -46,6 +46,8 @@ - (UIButton* )renderButton:(ACRView *)rootView [target createShowCard:inputs]; [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; return button; } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionSubmitRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionSubmitRenderer.mm index a10aa821fd..66c6c7cbbe 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionSubmitRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRActionSubmitRenderer.mm @@ -41,6 +41,8 @@ - (UIButton* )renderButton:(ACRView *)view [superview addTarget:target]; [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical]; + + [button setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal]; return button; } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.h index 92395a8c27..23d3cde2c3 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.h @@ -6,8 +6,6 @@ // #import -#import "SharedAdaptiveCard.h" -#import "HostConfig.h" #import "ACRView.h" @interface UIButton(ACRButton) diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.mm index 0c9b6b398e..47c131e49d 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRButton.mm @@ -7,10 +7,70 @@ #import "ACOBaseActionElementPrivate.h" #import "ACRButton.h" -#import "ACRView.h" +#import "ACRViewPrivate.h" +#import "ACRUIImageView.h" +#import "SharedAdaptiveCard.h" +#import "ACOHostConfigPrivate.h" @implementation UIButton(ACRButton) ++ (void)setImageView:(UIImage*)image inButton:(UIButton*)button withConfig:(ACOHostConfig *)config +{ + // Format the image so it fits in the button and is placed where it must be placed + CGSize contentSize = [button.titleLabel intrinsicContentSize]; + float imageHeight = contentSize.height; + IconPlacement iconPlacement = [config getHostConfig]->actions.iconPlacement; + + CGFloat widthToHeightRatio = 0.0f; + if(image){ + if(image.size.height > 0) { + widthToHeightRatio = image.size.width / image.size.height; + } + } + CGSize imageSize = CGSizeMake(imageHeight * widthToHeightRatio, imageHeight); + ACRUIImageView *imageView = [[ACRUIImageView alloc] initWithFrame:CGRectMake(0, 0, imageSize.width, imageSize.height)]; + imageView.translatesAutoresizingMaskIntoConstraints = NO; + imageView.image = image; + [button addSubview:imageView]; + // scale the image using UIImageView + [NSLayoutConstraint constraintWithItem:imageView + attribute:NSLayoutAttributeWidth + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:imageSize.width].active = YES; + + [NSLayoutConstraint constraintWithItem:imageView + attribute:NSLayoutAttributeHeight + relatedBy:NSLayoutRelationEqual + toItem:nil + attribute:NSLayoutAttributeNotAnAttribute + multiplier:1.0 + constant:imageSize.height].active = YES; + + if(iconPlacement == AdaptiveCards::IconPlacement::AboveTitle && config.allActionsHaveIcons) { + // fix image view to top and center x of the button + [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:button + attribute:NSLayoutAttributeTop multiplier:1.0 constant:config.buttonPadding].active = YES; + [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:button + attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0].active = YES; + // image can't be postion at the top of the title, so adjust title inset edges + [button setTitleEdgeInsets:UIEdgeInsetsMake(imageHeight, 0, -imageHeight, 0)]; + // readjust content edge, so intrinsic content size can be accurately determined by system library, and give enough room for title and image icon + [button setContentEdgeInsets:UIEdgeInsetsMake(config.buttonPadding, config.buttonPadding + button.layer.cornerRadius, config.buttonPadding + imageHeight, config.buttonPadding + button.layer.cornerRadius)]; + // configure button frame to correct size; in case translatesAutoresizingMaskIntoConstraints is used + button.frame = CGRectMake(0, 0, MAX(imageSize.width, contentSize.width), imageSize.height + config.buttonPadding); + } else { + int iconPadding = [config getHostConfig]->spacing.defaultSpacing; + [button setTitleEdgeInsets:UIEdgeInsetsMake(config.buttonPadding, (imageSize.width) + iconPadding, config.buttonPadding, -(iconPadding + imageSize.width))]; + [button setContentEdgeInsets:UIEdgeInsetsMake(config.buttonPadding, config.buttonPadding, config.buttonPadding, imageSize.width + iconPadding + button.layer.cornerRadius)]; + [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeLeft multiplier:1.0 constant:config.buttonPadding].active = YES; + [NSLayoutConstraint constraintWithItem:imageView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:button attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0].active = YES; + button.frame = CGRectMake(0, 0, imageSize.width + config.buttonPadding + contentSize.width, MAX(imageSize.height, contentSize.height)); + } +} + + (UIButton* )rootView:(ACRView *)rootView baseActionElement:(ACOBaseActionElement *)acoAction title:(NSString *)title @@ -20,41 +80,23 @@ + (UIButton* )rootView:(ACRView *)rootView NSBundle* bundle = [NSBundle bundleWithIdentifier:@"MSFT.AdaptiveCards"]; UIButton *button = [bundle loadNibNamed:@"ACRButton" owner:rootView options:nil][0]; [button setTitle:title forState:UIControlStateNormal]; - - CGSize contentSize = [button.titleLabel intrinsicContentSize]; - [button setFrame:CGRectMake(0, 0, contentSize.width, contentSize.height)]; + button.titleLabel.adjustsFontSizeToFitWidth = YES; std::shared_ptr action = [acoAction element]; - if([iconUrl length] != 0) - { - NSMutableDictionary *actionsViewMap = [rootView getActionsMap]; - __block UIImageView *imgView = nil; - // Generate key for ImageViewMap - NSString *key = [NSString stringWithCString:action->GetId().c_str() encoding:[NSString defaultCStringEncoding]]; - // Syncronize access to imageViewMap - dispatch_sync([rootView getSerialQueue], ^{ - // if imageView is available, get it, otherwise cache UIButton, so it can be used once images are ready - if(actionsViewMap[key] && [actionsViewMap[key] isKindOfClass:[UIImageView class]]) - { - imgView = actionsViewMap[key]; - } - else - { - actionsViewMap[key] = button; - } - }); - - if(imgView) - { - [ACRView setImageView:imgView inButton:button withConfig:config]; - - // remove postfix added for imageMap access - std::string id = action->GetId(); - std::size_t idx = id.find_last_of('_'); - action->SetId(id.substr(0, idx)); - } + NSDictionary *imageViewMap = [rootView getImageMap]; + NSString *key = [ACRView generateKeyForActionElement:action]; + UIImage *img = imageViewMap[key]; + + if(img){ + [UIButton setImageView:img inButton:button withConfig:config]; + } else { + // button's intrinsic content size is determined by title size and content edge + // add corner radius to content size by adding it to content edge inset + [button setContentEdgeInsets:UIEdgeInsetsMake(config.buttonPadding, config.buttonPadding + button.layer.cornerRadius, config.buttonPadding, config.buttonPadding + button.layer.cornerRadius)]; } return button; } + + @end diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRenderer.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRenderer.mm index 7e7f6f65fe..919f9b268f 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRenderer.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRRenderer.mm @@ -71,21 +71,22 @@ + (UIView *)renderWithAdaptiveCards:(std::shared_ptr const &)adapt [verticalView setStyle:style]; [rootView addTasksToConcurrentQueue:body]; - + + std::vector> actions = adaptiveCard->GetActions(); + + if(!actions.empty()) { + [rootView loadImagesForActionsAndCheckIfAllActionsHaveIconImages:actions hostconfig:config]; + } + [rootView waitForAsyncTasksToFinish]; [ACRRenderer render:verticalView rootView:rootView inputs:inputs withCardElems:body andHostConfig:config]; - [[rootView card] setInputs:inputs]; + [[rootView card] setInputs:inputs]; - std::vector> actions = adaptiveCard->GetActions(); if(!actions.empty()) { [ACRSeparator renderActionsSeparator:verticalView hostConfig:[config getHostConfig]]; - - [rootView addActionsToConcurrentQueue:actions]; - - [rootView waitForAsyncTasksToFinish]; - + // renders buttons and their associated actions [ACRRenderer renderButton:rootView inputs:inputs superview:verticalView actionElems:actions hostConfig:config]; } @@ -104,7 +105,8 @@ + (UIView *)renderWithAdaptiveCards:(std::shared_ptr const &)adapt ACRRegistration *reg = [ACRRegistration getInstance]; UIView *childview = nil; NSDictionary *attributes = - @{@"spacing":[NSNumber numberWithInt:[config getHostConfig]->actions.buttonSpacing]}; + @{@"spacing":[NSNumber numberWithInt:[config getHostConfig]->actions.buttonSpacing], + @"distribution":[NSNumber numberWithInt:UIStackViewDistributionFillProportionally] }; if(ActionsOrientation::Horizontal == [config getHostConfig]->actions.actionsOrientation){ childview = [[ACRColumnSetView alloc] initWithFrame:CGRectMake(0, 0, superview.frame.size.width, superview.frame.size.height) attributes:attributes]; @@ -147,7 +149,6 @@ + (UIView *)renderWithAdaptiveCards:(std::shared_ptr const &)adapt contentWidth = maxWidth; } childview.frame = CGRectMake(0, 0, contentWidth, contentHeight); - containingView.frame = CGRectMake(0, 0, superview.frame.size.width, contentHeight); containingView.translatesAutoresizingMaskIntoConstraints = NO; [containingView addSubview:childview]; @@ -187,9 +188,9 @@ + (UIView *)render:(UIView *)view { ACRRegistration *reg = [ACRRegistration getInstance]; ACOBaseCardElement *acoElem = [[ACOBaseCardElement alloc] init]; - + UIView *prevStretchableElem = nil, *curStretchableElem = nil; - + auto firstelem = elems.begin(); for(const auto &elem:elems) { @@ -207,9 +208,9 @@ + (UIView *)render:(UIView *)view } [acoElem setElem:elem]; - + curStretchableElem = [renderer render:view rootView:rootView inputs:inputs baseCardElement:acoElem hostConfig:config]; - + if(elem->GetHeight() == HeightType::Stretch){ if(prevStretchableElem){ NSLayoutConstraint *heightConstraint = [NSLayoutConstraint constraintWithItem:curStretchableElem @@ -222,12 +223,12 @@ + (UIView *)render:(UIView *)view heightConstraint.priority = UILayoutPriorityDefaultLow; heightConstraint.active = YES; } - + if([view isKindOfClass:[ACRColumnView class]]){ ACRColumnView *columnView = (ACRColumnView*)view; columnView.hasStretchableView = YES; } - + prevStretchableElem = curStretchableElem; } } diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm index 8ce8343bf3..8928f9efbd 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRView.mm @@ -33,7 +33,6 @@ @implementation ACRView ACOHostConfig *_hostConfig; NSMutableDictionary *_imageViewMap; NSMutableDictionary *_textMap; - NSMutableDictionary *_actionsMap; dispatch_queue_t _serial_queue; dispatch_queue_t _serial_text_queue; dispatch_queue_t _global_queue; @@ -49,7 +48,6 @@ - (instancetype)initWithFrame:(CGRect)frame _hostConfig = [[ACOHostConfig alloc] initWithConfig:cHostConfig]; _imageViewMap = [[NSMutableDictionary alloc] init]; _textMap = [[NSMutableDictionary alloc] init]; - _actionsMap = [[NSMutableDictionary alloc] init]; _serial_queue = dispatch_queue_create("io.adaptiveCards.serial_queue", DISPATCH_QUEUE_SERIAL); _serial_text_queue = dispatch_queue_create("io.adaptiveCards.serial_text_queue", DISPATCH_QUEUE_SERIAL); _global_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); @@ -204,8 +202,10 @@ - (void)addTasksToConcurrentQueue:(std::vector> { /// tag a base card element with unique key std::shared_ptrimgElem = std::static_pointer_cast(elem); - // dispatch to concurrent queue - [self processImageConcurrently:imgElem]; + NSString *urlStr = [NSString stringWithCString:imgElem->GetUrl().c_str() + encoding:[NSString defaultCStringEncoding]]; + NSString *key = [ACRView generateKeyForElement:imgElem]; + [self loadImage:urlStr key:key]; break; } case CardElementType::ImageSet: @@ -216,8 +216,10 @@ - (void)addTasksToConcurrentQueue:(std::vector> img->SetImageSize(imgSetElem->GetImageSize()); if([rendererRegistration isElementRendererOverriden:(ACRCardElementType) CardElementType::Image] == NO){ - /// tag a base card element with unique key - [self processImageConcurrently:img]; + NSString *urlStr = [NSString stringWithCString:img->GetUrl().c_str() + encoding:[NSString defaultCStringEncoding]]; + NSString *key = [ACRView generateKeyForElement:img]; + [self loadImage:urlStr key:key]; } } break; @@ -259,21 +261,15 @@ - (void)addTasksToConcurrentQueue:(std::vector> } // Walk through the actions found and process them concurrently -- (void)addActionsToConcurrentQueue:(std::vector> const &)actions +- (void)loadImagesForActionsAndCheckIfAllActionsHaveIconImages:(std::vector> const &)actions hostconfig:(ACOHostConfig *)hostConfig; { - BOOL allActionsHaveIcons = YES; - for(const auto &action : actions){ - if( action->GetIconUrl().empty() ){ - allActionsHaveIcons = NO; - break; - } - } - for(auto &action : actions){ - std::string iconUrl = action->GetIconUrl(); - if(!iconUrl.empty()){ - [self tagBaseActionElement:action]; - [self processActionWithIconConcurrently:action andAllActionsHaveIcons:allActionsHaveIcons]; + NSString *urlStr = [NSString stringWithCString:action->GetIconUrl().c_str() encoding:[NSString defaultCStringEncoding]]; + if([urlStr length]) { + NSString *key = [ACRView generateKeyForActionElement:action]; + [self loadImage:urlStr key:key]; + } else { + hostConfig.allActionsHaveIcons = NO; } } } @@ -361,80 +357,21 @@ - (void)processTextConcurrently:(std::shared_ptr const &)textEl }); } -- (void)processImageConcurrently:(std::shared_ptr const &)imageElem +- (void)loadImage:(NSString *)url key:(NSString *)key { - /// generate a string key to uniquely identify Image - std::shared_ptr imgElem = imageElem; - // run image downloading and processing on global queue which is concurrent and different from main queue dispatch_group_async(_async_tasks_group, _global_queue, ^{ - NSString *urlStr = [NSString stringWithCString:imgElem->GetUrl().c_str() - encoding:[NSString defaultCStringEncoding]]; - // generate key for imageMap from image element's id - NSURL *url = [NSURL URLWithString:urlStr]; + NSURL *nsurl = [NSURL URLWithString:url]; // download image - UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; + UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:nsurl]]; - NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)imgElem.get()]; - NSString *key = [number stringValue]; - dispatch_sync(self->_serial_queue, ^{self->_imageViewMap[key] = img; }); + dispatch_sync(self->_serial_queue, ^{self->_imageViewMap[key] = img;}); } ); } -- (void)processActionWithIconConcurrently:(std::shared_ptr const &)action andAllActionsHaveIcons:(BOOL)allActionsHaveIcons -{ - std::shared_ptr act = action; - /// generate a string key to uniquely identify Image - if(!(act->GetIconUrl().empty())) - { - // run image downloading and processing on global queue which is concurrent and different from main queue - dispatch_group_async(_async_tasks_group, _global_queue, - ^{ - NSString *urlStr = [NSString stringWithCString:act->GetIconUrl().c_str() encoding:[NSString defaultCStringEncoding]]; - // generate key for imageMap from image element's id - NSString *key = [NSString stringWithCString:act->GetId().c_str() encoding:[NSString defaultCStringEncoding]]; - NSURL *url = [NSURL URLWithString:urlStr]; - - // download image - UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; - ACRUIImageView *imageView = [[ACRUIImageView alloc] initWithImage:img]; - - // UITask can't be run on global queue, add task to main queue - dispatch_async(dispatch_get_main_queue(), - ^{ - __block UIButton *button = nil; - // synchronize access to image map - dispatch_sync(self->_serial_queue, - ^{ - if(!self->_actionsMap[key]) // UIButton is not ready, cache UIImageView - { - self->_actionsMap[key] = imageView; - } - else // UIButton ready, get view - { - button = self->_actionsMap[key]; - } - }); - - // if view is available, set image to it, and continue image processing - if(button) - { - [ACRView setImageView:imageView inButton:button withConfig:self->_hostConfig andAllActionsHaveIcons:allActionsHaveIcons]; - - // remove tag - std::string id = act->GetId(); - std::size_t idx = id.find_last_of('_'); - act->SetId(id.substr(0, idx)); - } - - }); - }); - } -} - // add postfix to existing BaseCardElement ID to be used as key --(void)tagBaseCardElement:(std::shared_ptr const &)elem +- (void)tagBaseCardElement:(std::shared_ptr const &)elem { std::string serial_number_as_string = std::to_string(_serialNumber); // concat a newly generated key to a existing id, the key will be removed after use @@ -442,13 +379,18 @@ -(void)tagBaseCardElement:(std::shared_ptr const &)elem ++_serialNumber; } -// add postfix to existing BaseCardElement ID to be used as key --(void)tagBaseActionElement:(std::shared_ptr const &)action ++ (NSString *)generateKeyForElement:(std::shared_ptr const &)elem { - std::string serial_number_as_string = std::to_string(_serialNumber); - // concat a newly generated key to a existing id, the key will be removed after use - action->SetId(action->GetId() + "_" + serial_number_as_string); - ++_serialNumber; + NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)elem.get()]; + NSString *key = [number stringValue]; + return key; +} + ++ (NSString *)generateKeyForActionElement:(std::shared_ptr const &)elem +{ + NSNumber *number = [NSNumber numberWithUnsignedLongLong:(unsigned long long)elem.get()]; + NSString *key = [number stringValue]; + return key; } - (NSMutableDictionary *)getImageMap @@ -465,39 +407,8 @@ - (NSMutableDictionary *)getTextMap return _textMap; } -- (NSMutableDictionary *)getActionsMap -{ - return _actionsMap; -} - - (ACOAdaptiveCard *)card { return _adaptiveCard; } - -+ (void)setImageView:(UIImageView*)imageView inButton:(UIButton*)button withConfig:(ACOHostConfig *)config andAllActionsHaveIcons:(BOOL)allActionsHaveIcons -{ - // Format the image so it fits in the button and is placed where it must be placed - CGSize contentSize = [button.titleLabel intrinsicContentSize]; - double imageHeight = contentSize.height; - CGSize originalImageSize = [imageView intrinsicContentSize]; - double scaleRatio = imageHeight / originalImageSize.height; - double imageWidth = scaleRatio * originalImageSize.width; - - IconPlacement iconPlacement = [config getHostConfig]->actions.iconPlacement; - if(iconPlacement == AdaptiveCards::IconPlacement::AboveTitle && allActionsHaveIcons) - { - [imageView setFrame:CGRectMake( (button.frame.size.width - imageWidth) / 2, 5, imageWidth, imageHeight)]; - [button setTitleEdgeInsets:UIEdgeInsetsMake(imageHeight, 5, -imageHeight, 5)]; - [button setContentEdgeInsets:UIEdgeInsetsMake(5, 5, 5 + imageHeight, 5)]; - } - else - { - int iconPadding = [config getHostConfig]->spacing.defaultSpacing; - [button setTitleEdgeInsets:UIEdgeInsetsMake(5, (iconPadding + imageWidth), 5, 0)]; - double titleOriginX = button.titleLabel.frame.origin.x; - [imageView setFrame:CGRectMake( titleOriginX - (iconPadding + imageWidth) / 2, 5, imageWidth, imageHeight)]; - } - [button addSubview:imageView]; -} @end diff --git a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRViewPrivate.h b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRViewPrivate.h index 6fa88f3869..37aac33571 100644 --- a/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRViewPrivate.h +++ b/source/ios/AdaptiveCards/AdaptiveCards/AdaptiveCards/ACRViewPrivate.h @@ -13,7 +13,12 @@ // Walk through adaptive cards elements and if images are found, download and process images concurrently and on different thread // from main thread, so images process won't block UI thread. -- (void) addTasksToConcurrentQueue:(std::vector> const &) body; -// Different method to just handle the actions so they wont be processed multiple times -- (void) addActionsToConcurrentQueue:(std::vector> const &) actions; +- (void)addTasksToConcurrentQueue:(std::vector> const &) body; +// async method +- (void)loadImagesForActionsAndCheckIfAllActionsHaveIconImages:(std::vector> const &)actions hostconfig:(ACOHostConfig *)hostConfig; + ++ (NSString *)generateKeyForElement:(std::shared_ptr const &)elem; + ++ (NSString *)generateKeyForActionElement:(std::shared_ptr const &)elem; + @end