99#import " SDAnimatedImageInterface.h"
1010#if SD_WATCH
1111
12- #pragma mark - SPI
12+ #import < objc/runtime.h>
13+ #import < objc/message.h>
1314
14- static UIImage * SharedEmptyImage (void ) {
15- // This is used for placeholder on `WKInterfaceImage`
16- // Do not using `[UIImage new]` because WatchKit will ignore it
17- static dispatch_once_t onceToken;
18- static UIImage *image;
19- dispatch_once (&onceToken, ^{
20- UIColor *color = UIColor.clearColor ;
21- CGRect rect = WKInterfaceDevice.currentDevice .screenBounds ;
22- UIGraphicsBeginImageContext (rect.size );
23- CGContextRef context = UIGraphicsGetCurrentContext ();
24- CGContextSetFillColorWithColor (context, [color CGColor ]);
25- CGContextFillRect (context, rect);
26- image = UIGraphicsGetImageFromCurrentImageContext ();
27- UIGraphicsEndImageContext ();
28- });
29- return image;
30- }
15+ #pragma mark - SPI
3116
3217@protocol CALayerProtocol <NSObject >
3318@property (nullable , strong ) id contents;
@@ -43,6 +28,22 @@ @protocol UIViewProtocol <NSObject>
4328@property (nonatomic ) CGFloat alpha;
4429@property (nonatomic , getter =isHidden) BOOL hidden;
4530@property (nonatomic , getter =isOpaque) BOOL opaque;
31+ @property (nonatomic ) CGRect frame;
32+ @property (nonatomic ) CGRect bounds;
33+ @property (nonatomic ) CGPoint center;
34+ @property (nonatomic ) BOOL clipsToBounds;
35+ @property (nonatomic , readonly ) CGSize intrinsicContentSize;
36+ @property (nonatomic ) NSInteger tag;
37+
38+ - (void )invalidateIntrinsicContentSize ;
39+ - (void )drawRect : (CGRect)rect ;
40+ - (void )setNeedsDisplay ;
41+ - (void )setNeedsDisplayInRect : (CGRect)rect ;
42+ - (void )addSubview : (id <UIViewProtocol>)view ;
43+ - (void )removeFromSuperview ;
44+ - (void )layoutSubviews ;
45+ - (CGSize)sizeThatFits : (CGSize)size ;
46+ - (void )sizeToFit ;
4647
4748@end
4849
@@ -60,7 +61,7 @@ @interface WKInterfaceObject ()
6061// This is needed for dynamic created WKInterfaceObject, like `WKInterfaceMap`
6162- (instancetype )_initForDynamicCreationWithInterfaceProperty : (NSString *)property ;
6263// This is remote UIView
63- @property (nonatomic , strong , readonly ) id <UIImageViewProtocol > _interfaceView;
64+ @property (nonatomic , strong , readwrite ) id <UIViewProtocol > _interfaceView;
6465
6566@end
6667
@@ -97,7 +98,6 @@ - (NSDictionary *)interfaceDescriptionForDynamicCreation {
9798 return @{
9899 @" type" : @" image" ,
99100 @" property" : self.interfaceProperty ,
100- @" image" : SharedEmptyImage ()
101101 };
102102}
103103
@@ -113,8 +113,7 @@ - (void)setImage:(UIImage *)image {
113113 self.currentFrameIndex = 0 ;
114114 self.currentLoopCount = 0 ;
115115
116- [super setImage: image];
117- [self _interfaceView ].image = image;
116+ ((id <UIImageViewProtocol>)[self _interfaceView ]).image = image;
118117 if ([image.class conformsToProtocol: @protocol (SDAnimatedImage)]) {
119118 // Create animted player
120119 self.player = [SDAnimatedImagePlayer playerWithProvider: (id <SDAnimatedImage>)image];
@@ -257,5 +256,142 @@ - (void)sd_setImageWithURL:(nullable NSURL *)url
257256 }];
258257}
259258
259+ @end
260+
261+
262+ #define SDAnimatedImageInterfaceWrapperTag 123456789
263+ #define SDAnimatedImageInterfaceWrapperSEL_layoutSubviews @" SDAnimatedImageInterfaceWrapper_layoutSubviews"
264+ #define SDAnimatedImageInterfaceWrapperSEL_sizeThatFits @" SDAnimatedImageInterfaceWrapper_sizeThatFits:"
265+
266+ // This using hook to implements the same logic like AnimatedImageViewWrapper.swift
267+ static CGSize intrinsicContentSizeIMP (id <UIViewProtocol> self, SEL _cmd) {
268+ struct objc_super superClass = {
269+ self,
270+ [self superclass ]
271+ };
272+ NSUInteger tag = self.tag ;
273+ id <UIViewProtocol> interfaceView = self.subviews .firstObject ;
274+ if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
275+ return ((CGSize (*)(id , SEL ))objc_msgSendSuper)((__bridge id )(&superClass), _cmd);
276+ }
277+ CGSize size = interfaceView.intrinsicContentSize ;
278+ if (size.width > 0 && size.height > 0 ) {
279+ CGFloat aspectRatio = size.height / size.width ;
280+ return CGSizeMake (1 , 1 * aspectRatio);
281+ } else {
282+ return CGSizeMake (-1 , -1 );
283+ }
284+ }
285+
286+ static void layoutSubviewsIMP (id <UIViewProtocol> self, SEL _cmd) {
287+ struct objc_super superClass = {
288+ self,
289+ [self superclass ]
290+ };
291+ NSUInteger tag = self.tag ;
292+ id <UIViewProtocol> interfaceView = self.subviews .firstObject ;
293+ if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
294+ ((void (*)(id , SEL ))objc_msgSend)(self, NSSelectorFromString (SDAnimatedImageInterfaceWrapperSEL_layoutSubviews));
295+ return ;
296+ }
297+ ((void (*)(id , SEL ))objc_msgSendSuper)((__bridge id )(&superClass), _cmd);
298+ interfaceView.frame = self.bounds ;
299+ }
300+
301+ // This is suck that SwiftUI on watchOS will call extra sizeThatFits, we should always input size (already calculated with aspectRatio)
302+ // iOS's wrapper don't need this
303+ static CGSize sizeThatFitsIMP (id <UIViewProtocol> self, SEL _cmd, CGSize size) {
304+ NSUInteger tag = self.tag ;
305+ id <UIViewProtocol> interfaceView = self.subviews .firstObject ;
306+ if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
307+ return ((CGSize (*)(id , SEL ))objc_msgSend)(self, NSSelectorFromString (SDAnimatedImageInterfaceWrapperSEL_sizeThatFits));
308+ }
309+ return size;
310+ }
311+
312+ @implementation SDAnimatedImageInterfaceWrapper
313+
314+ + (void )load {
315+ static dispatch_once_t onceToken;
316+ dispatch_once (&onceToken, ^{
317+ Class class = NSClassFromString (@" SPInterfaceGroupView" );
318+ // Implements `intrinsicContentSize`
319+ SEL selector = @selector (intrinsicContentSize );
320+ Method method = class_getInstanceMethod (class, selector);
321+
322+ BOOL didAddMethod =
323+ class_addMethod (class,
324+ selector,
325+ (IMP )intrinsicContentSizeIMP,
326+ method_getTypeEncoding (method));
327+ if (!didAddMethod) {
328+ NSAssert (NO , @" SDAnimatedImageInterfaceWrapper will not work as expected." );
329+ }
330+
331+ // Override `layoutSubviews`
332+ SEL originalSelector = @selector (layoutSubviews );
333+ SEL swizzledSelector = NSSelectorFromString (SDAnimatedImageInterfaceWrapperSEL_layoutSubviews);
334+ Method originalMethod = class_getInstanceMethod (class, originalSelector);
335+
336+ didAddMethod =
337+ class_addMethod (class,
338+ swizzledSelector,
339+ (IMP )layoutSubviewsIMP,
340+ method_getTypeEncoding (originalMethod));
341+ if (!didAddMethod) {
342+ NSAssert (NO , @" SDAnimatedImageInterfaceWrapper will not work as expected." );
343+ } else {
344+ Method swizzledMethod = class_getInstanceMethod (class, swizzledSelector);
345+ method_exchangeImplementations (originalMethod, swizzledMethod);
346+ }
347+
348+ // Override `sizeThatFits:`
349+ originalSelector = @selector (sizeThatFits: );
350+ swizzledSelector = NSSelectorFromString (SDAnimatedImageInterfaceWrapperSEL_sizeThatFits);
351+ originalMethod = class_getInstanceMethod (class, originalSelector);
352+
353+ didAddMethod =
354+ class_addMethod (class,
355+ swizzledSelector,
356+ (IMP )sizeThatFitsIMP,
357+ method_getTypeEncoding (originalMethod));
358+ if (!didAddMethod) {
359+ NSAssert (NO , @" SDAnimatedImageInterfaceWrapper will not work as expected." );
360+ } else {
361+ Method swizzledMethod = class_getInstanceMethod (class, swizzledSelector);
362+ method_exchangeImplementations (originalMethod, swizzledMethod);
363+ }
364+ });
365+ }
366+
367+ - (instancetype )init {
368+ Class cls = [self class ];
369+ NSString *UUID = [NSUUID UUID ].UUIDString ;
370+ NSString *property = [NSString stringWithFormat: @" %@ _%@ " , cls, UUID];
371+ self = [self _initForDynamicCreationWithInterfaceProperty: property];
372+ if (self) {
373+ self.wrapped = [[SDAnimatedImageInterface alloc ] init ];
374+ }
375+ return self;
376+ }
377+
378+ - (NSDictionary *)interfaceDescriptionForDynamicCreation {
379+ // This is called by WatchKit to provide default value
380+ return @{
381+ @" type" : @" group" ,
382+ @" property" : self.interfaceProperty ,
383+ @" radius" : @(0 ),
384+ @" items" : @[self .wrapped.interfaceDescriptionForDynamicCreation], // This will create the native view and added to subview
385+ };
386+ }
387+
388+ - (void )set_interfaceView : (id <UIViewProtocol>)interfaceView {
389+ // This is called by WatchKit when native view created
390+ [super set_interfaceView: interfaceView];
391+ // Bind the interface object and native view
392+ interfaceView.tag = SDAnimatedImageInterfaceWrapperTag;
393+ self.wrapped ._interfaceView = interfaceView.subviews .firstObject ;
394+ }
395+
260396@end
261397#endif
0 commit comments