Using a new super evil hack solution on watchOS, solve the problem that aspectRatio does not works as expected.
This code by using the native UIView and logic by reverse engineering
This commit is contained in:
parent
996f65ea15
commit
a2a54ec329
|
@ -14,12 +14,7 @@ import SDWebImageSwiftUIObjC
|
|||
|
||||
// Convenient
|
||||
#if os(watchOS)
|
||||
public typealias AnimatedImageViewWrapper = SDAnimatedImageInterface
|
||||
extension SDAnimatedImageInterface {
|
||||
var wrapped: SDAnimatedImageInterface {
|
||||
return self
|
||||
}
|
||||
}
|
||||
public typealias AnimatedImageViewWrapper = SDAnimatedImageInterfaceWrapper
|
||||
#endif
|
||||
|
||||
// Coordinator Life Cycle Binding Object
|
||||
|
@ -562,19 +557,7 @@ extension AnimatedImage {
|
|||
var result = self
|
||||
result.aspectRatio = aspectRatio
|
||||
result.contentMode = contentMode
|
||||
#if os(macOS) || os(iOS) || os(tvOS)
|
||||
return result.modifier(EmptyModifier()).aspectRatio(aspectRatio, contentMode: contentMode)
|
||||
#else
|
||||
return Group {
|
||||
if aspectRatio != nil {
|
||||
result.modifier(EmptyModifier()).aspectRatio(aspectRatio, contentMode: contentMode)
|
||||
} else {
|
||||
// on watchOS, there are no workaround like `AnimatedImageViewWrapper` to override `intrinsicContentSize`, so the aspect ratio is undetermined and cause sizing issues
|
||||
// To workaround, we do not call default implementation for this case, using original solution instead
|
||||
result
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Constrains this view's dimensions to the aspect ratio of the given size.
|
||||
|
|
|
@ -29,5 +29,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@end
|
||||
|
||||
@interface SDAnimatedImageInterfaceWrapper : WKInterfaceGroup
|
||||
|
||||
@property (nonatomic, strong, nonnull) SDAnimatedImageInterface *wrapped;
|
||||
|
||||
- (instancetype)init WK_AVAILABLE_WATCHOS_ONLY(6.0);
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
#endif
|
||||
|
|
|
@ -9,25 +9,10 @@
|
|||
#import "SDAnimatedImageInterface.h"
|
||||
#if SD_WATCH
|
||||
|
||||
#pragma mark - SPI
|
||||
#import <objc/runtime.h>
|
||||
#import <objc/message.h>
|
||||
|
||||
static UIImage * SharedEmptyImage(void) {
|
||||
// This is used for placeholder on `WKInterfaceImage`
|
||||
// Do not using `[UIImage new]` because WatchKit will ignore it
|
||||
static dispatch_once_t onceToken;
|
||||
static UIImage *image;
|
||||
dispatch_once(&onceToken, ^{
|
||||
UIColor *color = UIColor.clearColor;
|
||||
CGRect rect = WKInterfaceDevice.currentDevice.screenBounds;
|
||||
UIGraphicsBeginImageContext(rect.size);
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
CGContextSetFillColorWithColor(context, [color CGColor]);
|
||||
CGContextFillRect(context, rect);
|
||||
image = UIGraphicsGetImageFromCurrentImageContext();
|
||||
UIGraphicsEndImageContext();
|
||||
});
|
||||
return image;
|
||||
}
|
||||
#pragma mark - SPI
|
||||
|
||||
@protocol CALayerProtocol <NSObject>
|
||||
@property (nullable, strong) id contents;
|
||||
|
@ -43,6 +28,22 @@ static UIImage * SharedEmptyImage(void) {
|
|||
@property (nonatomic) CGFloat alpha;
|
||||
@property (nonatomic, getter=isHidden) BOOL hidden;
|
||||
@property (nonatomic, getter=isOpaque) BOOL opaque;
|
||||
@property (nonatomic) CGRect frame;
|
||||
@property (nonatomic) CGRect bounds;
|
||||
@property (nonatomic) CGPoint center;
|
||||
@property (nonatomic) BOOL clipsToBounds;
|
||||
@property (nonatomic, readonly) CGSize intrinsicContentSize;
|
||||
@property(nonatomic) NSInteger tag;
|
||||
|
||||
- (void)invalidateIntrinsicContentSize;
|
||||
- (void)drawRect:(CGRect)rect;
|
||||
- (void)setNeedsDisplay;
|
||||
- (void)setNeedsDisplayInRect:(CGRect)rect;
|
||||
- (void)addSubview:(id<UIViewProtocol>)view;
|
||||
- (void)removeFromSuperview;
|
||||
- (void)layoutSubviews;
|
||||
- (CGSize)sizeThatFits:(CGSize)size;
|
||||
- (void)sizeToFit;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -60,7 +61,7 @@ static UIImage * SharedEmptyImage(void) {
|
|||
// This is needed for dynamic created WKInterfaceObject, like `WKInterfaceMap`
|
||||
- (instancetype)_initForDynamicCreationWithInterfaceProperty:(NSString *)property;
|
||||
// This is remote UIView
|
||||
@property (nonatomic, strong, readonly) id<UIImageViewProtocol> _interfaceView;
|
||||
@property (nonatomic, strong, readwrite) id<UIViewProtocol> _interfaceView;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -97,7 +98,6 @@ static UIImage * SharedEmptyImage(void) {
|
|||
return @{
|
||||
@"type" : @"image",
|
||||
@"property" : self.interfaceProperty,
|
||||
@"image" : SharedEmptyImage()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -113,8 +113,7 @@ static UIImage * SharedEmptyImage(void) {
|
|||
self.currentFrameIndex = 0;
|
||||
self.currentLoopCount = 0;
|
||||
|
||||
[super setImage:image];
|
||||
[self _interfaceView].image = image;
|
||||
((id<UIImageViewProtocol>)[self _interfaceView]).image = image;
|
||||
if ([image.class conformsToProtocol:@protocol(SDAnimatedImage)]) {
|
||||
// Create animted player
|
||||
self.player = [SDAnimatedImagePlayer playerWithProvider:(id<SDAnimatedImage>)image];
|
||||
|
@ -257,5 +256,142 @@ static UIImage * SharedEmptyImage(void) {
|
|||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#define SDAnimatedImageInterfaceWrapperTag 123456789
|
||||
#define SDAnimatedImageInterfaceWrapperSEL_layoutSubviews @"SDAnimatedImageInterfaceWrapper_layoutSubviews"
|
||||
#define SDAnimatedImageInterfaceWrapperSEL_sizeThatFits @" SDAnimatedImageInterfaceWrapper_sizeThatFits:"
|
||||
|
||||
// This using hook to implements the same logic like AnimatedImageViewWrapper.swift
|
||||
static CGSize intrinsicContentSizeIMP(id<UIViewProtocol> self, SEL _cmd) {
|
||||
struct objc_super superClass = {
|
||||
self,
|
||||
[self superclass]
|
||||
};
|
||||
NSUInteger tag = self.tag;
|
||||
id<UIViewProtocol> interfaceView = self.subviews.firstObject;
|
||||
if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
|
||||
return ((CGSize(*)(id, SEL))objc_msgSendSuper)((__bridge id)(&superClass), _cmd);
|
||||
}
|
||||
CGSize size = interfaceView.intrinsicContentSize;
|
||||
if (size.width > 0 && size.height > 0) {
|
||||
CGFloat aspectRatio = size.height / size.width;
|
||||
return CGSizeMake(1, 1 * aspectRatio);
|
||||
} else {
|
||||
return CGSizeMake(-1, -1);
|
||||
}
|
||||
}
|
||||
|
||||
static void layoutSubviewsIMP(id<UIViewProtocol> self, SEL _cmd) {
|
||||
struct objc_super superClass = {
|
||||
self,
|
||||
[self superclass]
|
||||
};
|
||||
NSUInteger tag = self.tag;
|
||||
id<UIViewProtocol> interfaceView = self.subviews.firstObject;
|
||||
if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
|
||||
((void(*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(SDAnimatedImageInterfaceWrapperSEL_layoutSubviews));
|
||||
return;
|
||||
}
|
||||
((void(*)(id, SEL))objc_msgSendSuper)((__bridge id)(&superClass), _cmd);
|
||||
interfaceView.frame = self.bounds;
|
||||
}
|
||||
|
||||
// This is suck that SwiftUI on watchOS will call extra sizeThatFits, we should always input size (already calculated with aspectRatio)
|
||||
// iOS's wrapper don't need this
|
||||
static CGSize sizeThatFitsIMP(id<UIViewProtocol> self, SEL _cmd, CGSize size) {
|
||||
NSUInteger tag = self.tag;
|
||||
id<UIViewProtocol> interfaceView = self.subviews.firstObject;
|
||||
if (tag != SDAnimatedImageInterfaceWrapperTag || !interfaceView) {
|
||||
return ((CGSize(*)(id, SEL))objc_msgSend)(self, NSSelectorFromString(SDAnimatedImageInterfaceWrapperSEL_sizeThatFits));
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
@implementation SDAnimatedImageInterfaceWrapper
|
||||
|
||||
+ (void)load {
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
Class class = NSClassFromString(@"SPInterfaceGroupView");
|
||||
// Implements `intrinsicContentSize`
|
||||
SEL selector = @selector(intrinsicContentSize);
|
||||
Method method = class_getInstanceMethod(class, selector);
|
||||
|
||||
BOOL didAddMethod =
|
||||
class_addMethod(class,
|
||||
selector,
|
||||
(IMP)intrinsicContentSizeIMP,
|
||||
method_getTypeEncoding(method));
|
||||
if (!didAddMethod) {
|
||||
NSAssert(NO, @"SDAnimatedImageInterfaceWrapper will not work as expected.");
|
||||
}
|
||||
|
||||
// Override `layoutSubviews`
|
||||
SEL originalSelector = @selector(layoutSubviews);
|
||||
SEL swizzledSelector = NSSelectorFromString(SDAnimatedImageInterfaceWrapperSEL_layoutSubviews);
|
||||
Method originalMethod = class_getInstanceMethod(class, originalSelector);
|
||||
|
||||
didAddMethod =
|
||||
class_addMethod(class,
|
||||
swizzledSelector,
|
||||
(IMP)layoutSubviewsIMP,
|
||||
method_getTypeEncoding(originalMethod));
|
||||
if (!didAddMethod) {
|
||||
NSAssert(NO, @"SDAnimatedImageInterfaceWrapper will not work as expected.");
|
||||
} else {
|
||||
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
|
||||
// Override `sizeThatFits:`
|
||||
originalSelector = @selector(sizeThatFits:);
|
||||
swizzledSelector = NSSelectorFromString(SDAnimatedImageInterfaceWrapperSEL_sizeThatFits);
|
||||
originalMethod = class_getInstanceMethod(class, originalSelector);
|
||||
|
||||
didAddMethod =
|
||||
class_addMethod(class,
|
||||
swizzledSelector,
|
||||
(IMP)sizeThatFitsIMP,
|
||||
method_getTypeEncoding(originalMethod));
|
||||
if (!didAddMethod) {
|
||||
NSAssert(NO, @"SDAnimatedImageInterfaceWrapper will not work as expected.");
|
||||
} else {
|
||||
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
|
||||
method_exchangeImplementations(originalMethod, swizzledMethod);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
Class cls = [self class];
|
||||
NSString *UUID = [NSUUID UUID].UUIDString;
|
||||
NSString *property = [NSString stringWithFormat:@"%@_%@", cls, UUID];
|
||||
self = [self _initForDynamicCreationWithInterfaceProperty:property];
|
||||
if (self) {
|
||||
self.wrapped = [[SDAnimatedImageInterface alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSDictionary *)interfaceDescriptionForDynamicCreation {
|
||||
// This is called by WatchKit to provide default value
|
||||
return @{
|
||||
@"type" : @"group",
|
||||
@"property" : self.interfaceProperty,
|
||||
@"radius" : @(0),
|
||||
@"items": @[self.wrapped.interfaceDescriptionForDynamicCreation], // This will create the native view and added to subview
|
||||
};
|
||||
}
|
||||
|
||||
- (void)set_interfaceView:(id<UIViewProtocol>)interfaceView {
|
||||
// This is called by WatchKit when native view created
|
||||
[super set_interfaceView:interfaceView];
|
||||
// Bind the interface object and native view
|
||||
interfaceView.tag = SDAnimatedImageInterfaceWrapperTag;
|
||||
self.wrapped._interfaceView = interfaceView.subviews.firstObject;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue