9
9
#import " SDAnimatedImageInterface.h"
10
10
#if SD_WATCH
11
11
12
- #pragma mark - SPI
12
+ #import < objc/runtime.h>
13
+ #import < objc/message.h>
13
14
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
31
16
32
17
@protocol CALayerProtocol <NSObject >
33
18
@property (nullable , strong ) id contents;
@@ -43,6 +28,22 @@ @protocol UIViewProtocol <NSObject>
43
28
@property (nonatomic ) CGFloat alpha;
44
29
@property (nonatomic , getter =isHidden) BOOL hidden;
45
30
@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 ;
46
47
47
48
@end
48
49
@@ -60,7 +61,7 @@ @interface WKInterfaceObject ()
60
61
// This is needed for dynamic created WKInterfaceObject, like `WKInterfaceMap`
61
62
- (instancetype )_initForDynamicCreationWithInterfaceProperty : (NSString *)property ;
62
63
// This is remote UIView
63
- @property (nonatomic , strong , readonly ) id <UIImageViewProtocol > _interfaceView;
64
+ @property (nonatomic , strong , readwrite ) id <UIViewProtocol > _interfaceView;
64
65
65
66
@end
66
67
@@ -97,7 +98,6 @@ - (NSDictionary *)interfaceDescriptionForDynamicCreation {
97
98
return @{
98
99
@" type" : @" image" ,
99
100
@" property" : self.interfaceProperty ,
100
- @" image" : SharedEmptyImage ()
101
101
};
102
102
}
103
103
@@ -113,8 +113,7 @@ - (void)setImage:(UIImage *)image {
113
113
self.currentFrameIndex = 0 ;
114
114
self.currentLoopCount = 0 ;
115
115
116
- [super setImage: image];
117
- [self _interfaceView ].image = image;
116
+ ((id <UIImageViewProtocol>)[self _interfaceView ]).image = image;
118
117
if ([image.class conformsToProtocol: @protocol (SDAnimatedImage)]) {
119
118
// Create animted player
120
119
self.player = [SDAnimatedImagePlayer playerWithProvider: (id <SDAnimatedImage>)image];
@@ -257,5 +256,142 @@ - (void)sd_setImageWithURL:(nullable NSURL *)url
257
256
}];
258
257
}
259
258
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
+
260
396
@end
261
397
#endif
0 commit comments