Files
flipper/iOS/Plugins/FlipperKitLayoutPlugin/FlipperKitLayoutComponentKitSupport/CKComponent+Sonar.mm
Pritesh Nandgaonkar 972277b031 Fix warnings of FlipperKit in iOS
Summary:
This diff fixes the warnings raised from FlipperKit. Also this diff updates the deployement target of FlipperKit  to iOS 10. Even wilde assumes the min deployment OS version to be iOS 10. In layout plugin we use [UIAccessibiltyTraiTabBar](https://developer.apple.com/documentation/uikit/uiaccessibility/uiaccessibilitytraits/1648592-tabbar), which assumes iOS 10 and above.

Partially fixes https://github.com/facebook/flipper/issues/803

Still there are some warnings which are coming from the dependencies outside of Flipper.

Reviewed By: passy

Differential Revision: D19941558

fbshipit-source-id: 31809fedb9aa297bc318b5af72e29e8444f0142f
2020-02-18 12:47:21 -08:00

344 lines
13 KiB
Plaintext

/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#if FB_SONARKIT_ENABLED
#import "CKComponent+Sonar.h"
#import <ComponentKit/CKComponentAccessibility.h>
#import <ComponentKit/CKComponentController.h>
#import <ComponentKit/CKComponentDebugController.h>
#import <ComponentKit/CKComponentInternal.h>
#import <ComponentKit/CKComponentSubclass.h>
#import <ComponentKit/CKComponentViewConfiguration.h>
#import <ComponentKit/CKMutex.h>
#import <FlipperKitLayoutPlugin/SKNamed.h>
#import <FlipperKitLayoutPlugin/SKObject.h>
#import <objc/message.h>
#import <objc/runtime.h>
#import "CKCenterLayoutComponent+Sonar.h"
#import "CKFlexboxComponent+Sonar.h"
#import "CKInsetComponent+Sonar.h"
#import "CKRatioLayoutComponent+Sonar.h"
#import "CKStatelessComponent+Sonar.h"
#import "FKDataStorageForLiveEditing.h"
#import "Utils.h"
/** This protocol isn't actually adopted anywhere, it just lets us use the SEL
* below */
@protocol SonarKitLayoutComponentKitOverrideInformalProtocol
- (NSString*)sonar_componentNameOverride;
- (NSString*)sonar_componentDecorationOverride;
- (NSArray<SKNamed<NSDictionary<NSString*, NSObject*>*>*>*)
sonar_additionalDataOverride;
@end
static BOOL AccessibilityContextIsDefault(
CKComponentAccessibilityContext accessibilityContext) {
return accessibilityContext == CKComponentAccessibilityContext();
}
static NSDictionary<NSString*, NSObject*>* AccessibilityContextDict(
CKComponentAccessibilityContext accessibilityContext) {
NSMutableDictionary<NSString*, NSObject*>* accessibilityDict =
[NSMutableDictionary new];
if (accessibilityContext.isAccessibilityElement != nil) {
accessibilityDict[@"isAccessibilityElement"] =
SKObject(@([accessibilityContext.isAccessibilityElement boolValue]));
}
if (accessibilityContext.accessibilityLabel.hasText()) {
accessibilityDict[@"accessibilityLabel"] =
SKObject(accessibilityContext.accessibilityLabel.value());
}
if (accessibilityContext.accessibilityHint.hasText()) {
accessibilityDict[@"accessibilityHint"] =
SKObject(accessibilityContext.accessibilityHint.value());
}
if (accessibilityContext.accessibilityValue.hasText()) {
accessibilityDict[@"accessibilityValue"] =
SKObject(accessibilityContext.accessibilityValue.value());
}
if (accessibilityContext.accessibilityTraits != nil) {
accessibilityDict[@"accessibilityTraits"] =
SKObject(@([accessibilityContext.accessibilityTraits integerValue]));
}
if (accessibilityContext.accessibilityComponentAction) {
accessibilityDict[@"accessibilityComponentAction.identifier"] = SKObject(
@(accessibilityContext.accessibilityComponentAction.identifier()
.c_str()));
}
return accessibilityDict;
}
FB_LINKABLE(CKComponent_Sonar)
@implementation CKComponent (Sonar)
static FKDataStorageForLiveEditing* _dataStorage;
static NSMutableSet<NSString*>* _swizzledClasses;
static CK::StaticMutex _mutex = CK_MUTEX_INITIALIZER;
+ (void)swizzleOriginalSEL:(SEL)originalSEL to:(SEL)replacementSEL {
Class targetClass = self;
Method original = class_getInstanceMethod(targetClass, originalSEL);
Method replacement = class_getInstanceMethod(targetClass, replacementSEL);
BOOL didAddMethod = class_addMethod(
targetClass,
originalSEL,
method_getImplementation(replacement),
method_getTypeEncoding(replacement));
if (didAddMethod) {
class_replaceMethod(
targetClass,
replacementSEL,
method_getImplementation(original),
method_getTypeEncoding(original));
} else {
method_exchangeImplementations(original, replacement);
}
}
- (NSString*)sonar_getName {
if ([self respondsToSelector:@selector(sonar_componentNameOverride)]) {
return [(id)self sonar_componentNameOverride];
}
auto const canBeReusedCounter = self.flipper_canBeReusedCounter;
if (canBeReusedCounter > 0) {
return [NSString stringWithFormat:@"%@ (Can be reused x%lu)",
NSStringFromClass([self class]),
(unsigned long)canBeReusedCounter];
}
return NSStringFromClass([self class]);
}
- (NSString*)sonar_getDecoration {
if ([self respondsToSelector:@selector(sonar_componentDecorationOverride)]) {
return [(id)self sonar_componentDecorationOverride];
}
return @"componentkit";
}
- (NSArray<SKNamed<NSDictionary<NSString*, NSObject*>*>*>*)sonar_getData {
static NSDictionary<NSNumber*, NSString*>* UIControlEventsEnumMap = @{
@(UIControlEventTouchDown) : @"UIControlEventTouchDown",
@(UIControlEventTouchDownRepeat) : @"UIControlEventTouchDownRepeat",
@(UIControlEventTouchDragInside) : @"UIControlEventTouchDragInside",
@(UIControlEventTouchDragOutside) : @"UIControlEventTouchDragOutside",
@(UIControlEventTouchDragEnter) : @"UIControlEventTouchDragEnter",
@(UIControlEventTouchDragExit) : @"UIControlEventTouchDragExit",
@(UIControlEventTouchUpInside) : @"UIControlEventTouchUpInside",
@(UIControlEventTouchUpOutside) : @"UIControlEventTouchUpOutside",
@(UIControlEventTouchCancel) : @"UIControlEventTouchTouchCancel",
@(UIControlEventValueChanged) : @"UIControlEventValueChanged",
@(UIControlEventPrimaryActionTriggered) :
@"UIControlEventPrimaryActionTriggered",
@(UIControlEventEditingDidBegin) : @"UIControlEventEditingDidBegin",
@(UIControlEventEditingChanged) : @"UIControlEventEditingChanged",
@(UIControlEventEditingDidEnd) : @"UIControlEventEditingDidEnd",
@(UIControlEventEditingDidEndOnExit) : @"UIControlEventEditingDidEndOnExit",
};
NSMutableArray<SKNamed<NSDictionary<NSString*, NSObject*>*>*>* data =
[NSMutableArray new];
[data addObject:[SKNamed newWithName:@"CKComponent"
withValue:@{
@"frame" : SKObject(self.viewContext.frame),
@"controller" : SKObject(
NSStringFromClass([self.controller class])),
@"size" : SKObject(ckcomponentSize([self size])),
}]];
auto const canBeReusedCounter = self.flipper_canBeReusedCounter;
if (canBeReusedCounter > 0) {
[data addObject:[SKNamed
newWithName:@"Convert to CKRenderComponent"
withValue:@{
@"This component can be reused" : SKObject([NSString
stringWithFormat:@"%lu times",
(unsigned long)
canBeReusedCounter])
}]];
}
if (self.viewContext.view) {
auto _actions = _CKComponentDebugControlActionsForComponent(self);
if (_actions.size() > 0) {
NSMutableDictionary<NSString*, NSObject*>* actions =
[NSMutableDictionary new];
for (NSNumber* controlEvent : [UIControlEventsEnumMap allKeys]) {
NSMutableArray<NSDictionary<NSString*, NSObject*>*>* responders =
[NSMutableArray new];
for (const auto& action : _actions) {
if ((action.first & [controlEvent integerValue]) == 0) {
continue;
}
for (auto responder : action.second) {
auto debugTarget = _CKTypedComponentDebugInitialTarget(responder);
if (debugTarget.isBlockBaseAction()) {
[responders addObject:@{
@"identifier" : SKObject(@(responder.identifier().c_str())),
@"selector" :
SKObject(NSStringFromSelector(responder.selector())),
}];
} else {
id initialTarget = debugTarget.get(self);
const CKActionInfo actionInfo =
CKActionFind(responder.selector(), initialTarget);
[responders addObject:@{
@"initialTarget" :
SKObject(NSStringFromClass([initialTarget class])),
@"identifier" : SKObject(@(responder.identifier().c_str())),
@"handler" :
SKObject(NSStringFromClass([actionInfo.responder class])),
@"selector" :
SKObject(NSStringFromSelector(responder.selector())),
}];
}
}
}
if (responders.count > 0) {
actions[UIControlEventsEnumMap[controlEvent]] = responders;
}
}
[data addObject:[SKNamed newWithName:@"Actions" withValue:actions]];
}
}
// Only add accessibility panel if accessibilityContext is not default
CKComponentAccessibilityContext accessibilityContext =
[self viewConfiguration].accessibilityContext();
if (!AccessibilityContextIsDefault(accessibilityContext)) {
[data addObject:[SKNamed
newWithName:@"Accessibility"
withValue:@{
@"accessibilityContext" :
AccessibilityContextDict(accessibilityContext),
@"accessibilityEnabled" : SKMutableObject(
@(CK::Component::Accessibility::
IsAccessibilityEnabled())),
}]];
}
if ([self respondsToSelector:@selector(sonar_additionalDataOverride)]) {
[data addObjectsFromArray:[(id)self sonar_additionalDataOverride]];
}
return data;
}
- (void)setMutableData:(id)value {
}
- (void)setMutableDataFromStorage {
const auto globalID = self.treeNode.nodeIdentifier;
id data = [_dataStorage dataForTreeNodeIdentifier:globalID];
if (data) {
[self setMutableData:data];
}
}
+ (NSString*)swizzledMethodNameForRender {
return
[NSString stringWithFormat:@"sonar_render_%@", NSStringFromClass(self)];
}
+ (SEL)registerNewImplementation:(SEL)selector {
SEL resultSelector =
sel_registerName([[self swizzledMethodNameForRender] UTF8String]);
Method method = class_getInstanceMethod(self, selector);
class_addMethod(
self,
resultSelector,
method_getImplementation(method),
method_getTypeEncoding(method));
return resultSelector;
}
- (CKComponent*)sonar_render:(id)state {
[self setMutableDataFromStorage];
SEL resultSelector =
NSSelectorFromString([[self class] swizzledMethodNameForRender]);
return ((CKComponent * (*)(CKComponent*, SEL, id)) objc_msgSend)(
self, resultSelector, state);
}
- (NSDictionary<NSString*, SKNodeDataChanged>*)sonar_getDataMutationsChanged {
return @{};
}
- (NSDictionary<NSString*, SKNodeUpdateData>*)sonar_getDataMutations {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_dataStorage = [[FKDataStorageForLiveEditing alloc] init];
_swizzledClasses = [[NSMutableSet alloc] init];
});
{
CK::StaticMutexLocker l(_mutex);
if (![_swizzledClasses containsObject:NSStringFromClass([self class])]) {
[_swizzledClasses addObject:NSStringFromClass([self class])];
if ([self respondsToSelector:@selector(render:)]) {
SEL replacement =
[[self class] registerNewImplementation:@selector(sonar_render:)];
[[self class] swizzleOriginalSEL:@selector(render:) to:replacement];
} else {
CKAssert(
NO,
@"Only CKRenderLayoutComponent and CKRenderLayoutWithChildrenComponent children are now able to be live editable");
}
}
}
NSDictionary<NSString*, SKNodeDataChanged>* dataChanged =
[self sonar_getDataMutationsChanged];
NSMutableDictionary* dataMutation = [[NSMutableDictionary alloc] init];
[dataMutation addEntriesFromDictionary:@{
@"Accessibility.accessibilityEnabled": ^(NSNumber *value) {
CK::Component::Accessibility::SetForceAccessibilityEnabled([value boolValue]);
}
}
];
const auto globalID = self.treeNode.nodeIdentifier;
for (NSString* key in dataChanged) {
const auto block = dataChanged[key];
[dataMutation
setObject:^(id value) {
id data = block(value);
[_dataStorage setData:data forTreeNodeIdentifier:globalID];
[CKComponentDebugController
reflowComponentsWithTreeNodeIdentifier:globalID];
}
forKey:key];
}
return dataMutation;
}
static char const kCanBeReusedKey = ' ';
- (void)setFlipper_canBeReusedCounter:(NSUInteger)canBeReusedCounter {
objc_setAssociatedObject(
self,
&kCanBeReusedKey,
@(canBeReusedCounter),
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSUInteger)flipper_canBeReusedCounter {
return [objc_getAssociatedObject(self, &kCanBeReusedKey)
unsignedIntegerValue];
}
@end
#endif