This commit is contained in:
2025-10-28 10:18:10 +08:00
parent efb04d134e
commit 1deca2ae5b
166 changed files with 17288 additions and 1427 deletions

View File

@@ -6,11 +6,16 @@
//
#import "KeyboardViewController.h"
#import "KBToolBar.h"
#import "KBKeyboardView.h"
#import "KBKey.h"
static CGFloat KEYBOARDHEIGHT = 256;
@interface KeyboardViewController ()
@property (nonatomic, strong) UIButton *nextKeyboardButton;
@interface KeyboardViewController () <KBToolBarDelegate, KBKeyboardViewDelegate>
@property (nonatomic, strong) UIButton *nextKeyboardButton; //
@property (nonatomic, strong) KBToolBar *topBar;
@property (nonatomic, strong) KBKeyboardView *keyboardView;
@end
@implementation KeyboardViewController
@@ -18,7 +23,7 @@ static CGFloat KEYBOARDHEIGHT = 256;
- (void)updateViewConstraints {
[super updateViewConstraints];
// Add custom view sizing constraints here
//
}
- (void)viewDidLoad {
@@ -43,14 +48,29 @@ static CGFloat KEYBOARDHEIGHT = 256;
- (void)setupUI {
CGFloat toolBarHeight = 40;
CGFloat bottom = 5;
CGFloat buttonSpace = 8;
CGFloat eachButtonHeight = (KEYBOARDHEIGHT - toolBarHeight - 10 - 8 * 3 - bottom) / 4;
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, 30)];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
// self.view.translatesAutoresizingMaskIntoConstraints = NO;
//
[self.view.heightAnchor constraintEqualToConstant:KEYBOARDHEIGHT].active = YES;
//
self.topBar = [[KBToolBar alloc] init];
self.topBar.delegate = self;
[self.view addSubview:self.topBar];
[self.topBar mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.top.equalTo(self.view.mas_top).offset(6);
make.height.mas_equalTo(40);
}];
//
self.keyboardView = [[KBKeyboardView alloc] init];
self.keyboardView.delegate = self;
[self.view addSubview:self.keyboardView];
[self.keyboardView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.right.equalTo(self.view);
make.top.equalTo(self.topBar.mas_bottom).offset(4);
make.bottom.equalTo(self.view.mas_bottom).offset(-4);
}];
}
@@ -61,11 +81,11 @@ static CGFloat KEYBOARDHEIGHT = 256;
}
- (void)textWillChange:(id<UITextInput>)textInput {
// The app is about to change the document's contents. Perform any preparation here.
//
}
- (void)textDidChange:(id<UITextInput>)textInput {
// The app has just changed the document's contents, the document context has been updated.
//
UIColor *textColor = nil;
if (self.textDocumentProxy.keyboardAppearance == UIKeyboardAppearanceDark) {
@@ -76,4 +96,46 @@ static CGFloat KEYBOARDHEIGHT = 256;
[self.nextKeyboardButton setTitleColor:textColor forState:UIControlStateNormal];
}
#pragma mark - KBToolBarDelegate
- (void)toolBar:(KBToolBar *)toolBar didTapActionAtIndex:(NSInteger)index {
// [A1]..[A4]
NSString *placeholder = [NSString stringWithFormat:@"[A%ld]", (long)index+1];
[self.textDocumentProxy insertText:placeholder];
}
- (void)toolBarDidTapSettings:(KBToolBar *)toolBar {
// openURL 宿
//
[self.textDocumentProxy insertText:@"[settings]"];
}
#pragma mark - KBKeyboardViewDelegate
- (void)keyboardView:(KBKeyboardView *)keyboard didTapKey:(KBKey *)key {
switch (key.type) {
case KBKeyTypeCharacter:
[self.textDocumentProxy insertText:key.output ?: key.title ?: @""]; break;
case KBKeyTypeBackspace:
[self.textDocumentProxy deleteBackward]; break;
case KBKeyTypeSpace:
[self.textDocumentProxy insertText:@" "]; break;
case KBKeyTypeReturn:
[self.textDocumentProxy insertText:@"\n"]; break;
case KBKeyTypeModeChange: {
// <->
keyboard.layoutStyle = (keyboard.layoutStyle == KBKeyboardLayoutStyleLetters) ? KBKeyboardLayoutStyleNumbers : KBKeyboardLayoutStyleLetters;
[keyboard reloadKeys];
} break;
case KBKeyTypeGlobe:
[self advanceToNextInputMode]; break;
case KBKeyTypeCustom:
//
[self.textDocumentProxy insertText:@"[lang]"]; break;
case KBKeyTypeShift:
// Shift KBKeyboardView
break;
}
}
@end

View File

@@ -0,0 +1,31 @@
//
// KBKey.h
// CustomKeyboard
//
// 简单的键位数据模型,用于描述键盘上的一个键。
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, KBKeyType) {
KBKeyTypeCharacter = 0, // 普通字符输出
KBKeyTypeBackspace, // 删除
KBKeyTypeShift, // 大小写切换
KBKeyTypeModeChange, // 模式切换(如 123/ABC
KBKeyTypeSpace, // 空格
KBKeyTypeReturn, // 回车/发送
KBKeyTypeGlobe, // 系统地球键
KBKeyTypeCustom // 自定义功能占位
};
@interface KBKey : NSObject
@property (nonatomic, assign) KBKeyType type;
@property (nonatomic, copy) NSString *title; // 显示标题
@property (nonatomic, copy) NSString *output; // 字符键插入的文本
+ (instancetype)keyWithTitle:(NSString *)title output:(NSString *)output;
+ (instancetype)keyWithTitle:(NSString *)title type:(KBKeyType)type;
@end

View File

@@ -0,0 +1,27 @@
//
// KBKey.m
// CustomKeyboard
//
#import "KBKey.h"
@implementation KBKey
+ (instancetype)keyWithTitle:(NSString *)title output:(NSString *)output {
KBKey *k = [[KBKey alloc] init];
k.type = KBKeyTypeCharacter;
k.title = title ?: @"";
k.output = output ?: title ?: @"";
return k;
}
+ (instancetype)keyWithTitle:(NSString *)title type:(KBKeyType)type {
KBKey *k = [[KBKey alloc] init];
k.type = type;
k.title = title ?: @"";
k.output = @"";
return k;
}
@end

View File

@@ -12,7 +12,7 @@
// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file.
#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define imageNamed(s) [UIImage imageNamed:s]
#import "Masonry.h"

View File

@@ -0,0 +1,17 @@
//
// KBKeyButton.h
// CustomKeyboard
//
#import <UIKit/UIKit.h>
@class KBKey;
/// 自定义键按钮UIButton 子类):圆角外观,按下高亮效果。
@interface KBKeyButton : UIButton
@property (nonatomic, strong) KBKey *key;
/// 配置基础样式(背景、圆角等)。创建按钮时调用。
- (void)applyDefaultStyle;
@end

View File

@@ -0,0 +1,36 @@
//
// KBKeyButton.m
// CustomKeyboard
//
#import "KBKeyButton.h"
#import "KBKey.h"
@implementation KBKeyButton
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self applyDefaultStyle];
}
return self;
}
- (void)applyDefaultStyle {
self.titleLabel.font = [UIFont systemFontOfSize:18 weight:UIFontWeightSemibold]; //
[self setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[self setTitleColor:[UIColor blackColor] forState:UIControlStateHighlighted];
self.backgroundColor = [UIColor whiteColor];
self.layer.cornerRadius = 6.0; //
self.layer.masksToBounds = NO;
self.layer.shadowColor = [UIColor colorWithWhite:0 alpha:0.1].CGColor; //
self.layer.shadowOpacity = 1.0;
self.layer.shadowOffset = CGSizeMake(0, 1);
self.layer.shadowRadius = 1.5;
}
- (void)setHighlighted:(BOOL)highlighted {
[super setHighlighted:highlighted];
self.alpha = highlighted ? 0.7 : 1.0; //
}
@end

View File

@@ -0,0 +1,31 @@
//
// KBKeyboardView.h
// CustomKeyboard
//
// 键盘主容器,内部管理按键行布局。
//
#import <UIKit/UIKit.h>
@class KBKeyboardView, KBKey;
typedef NS_ENUM(NSInteger, KBKeyboardLayoutStyle) {
KBKeyboardLayoutStyleLetters = 0,
KBKeyboardLayoutStyleNumbers
};
@protocol KBKeyboardViewDelegate <NSObject>
@optional
/// 键被点击的回调
- (void)keyboardView:(KBKeyboardView *)keyboard didTapKey:(KBKey *)key;
@end
@interface KBKeyboardView : UIView
@property (nonatomic, weak) id<KBKeyboardViewDelegate> delegate;
@property (nonatomic, assign) KBKeyboardLayoutStyle layoutStyle; // 布局样式(字母/数字)
@property (nonatomic, assign, getter=isShiftOn) BOOL shiftOn; // 大小写状态
- (void)reloadKeys; // 当布局样式/大小写变化时调用
@end

View File

@@ -0,0 +1,255 @@
//
// KBKeyboardView.m
// CustomKeyboard
//
#import "KBKeyboardView.h"
#import "KBKeyButton.h"
#import "KBKey.h"
@interface KBKeyboardView ()
@property (nonatomic, strong) UIView *row1;
@property (nonatomic, strong) UIView *row2;
@property (nonatomic, strong) UIView *row3;
@property (nonatomic, strong) UIView *row4;
@property (nonatomic, strong) NSArray<NSArray<KBKey *> *> *keysForRows;
@end
@implementation KBKeyboardView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
_layoutStyle = KBKeyboardLayoutStyleLetters;
_shiftOn = YES; // 使
[self buildBase];
[self reloadKeys];
}
return self;
}
- (void)buildBase {
[self addSubview:self.row1];
[self addSubview:self.row2];
[self addSubview:self.row3];
[self addSubview:self.row4];
CGFloat vSpacing = 8;
[self.row1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.mas_top).offset(8);
make.left.right.equalTo(self);
make.height.mas_equalTo(44);
}];
[self.row2 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.row1.mas_bottom).offset(vSpacing);
make.left.right.equalTo(self);
make.height.equalTo(self.row1);
}];
[self.row3 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.row2.mas_bottom).offset(vSpacing);
make.left.right.equalTo(self);
make.height.equalTo(self.row1);
}];
[self.row4 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.row3.mas_bottom).offset(vSpacing);
make.left.right.equalTo(self);
make.height.equalTo(self.row1);
make.bottom.equalTo(self.mas_bottom).offset(-6);
}];
}
- (void)reloadKeys {
//
for (UIView *row in @[self.row1, self.row2, self.row3, self.row4]) {
[row.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
}
self.keysForRows = [self buildKeysForCurrentLayout];
[self buildRow:self.row1 withKeys:self.keysForRows[0]];
//
CGFloat row2Spacer = (self.layoutStyle == KBKeyboardLayoutStyleLetters) ? 0.5 : 0.0;
[self buildRow:self.row2 withKeys:self.keysForRows[1] edgeSpacerMultiplier:row2Spacer];
[self buildRow:self.row3 withKeys:self.keysForRows[2]];
[self buildRow:self.row4 withKeys:self.keysForRows[3]];
}
- (NSArray<NSArray<KBKey *> *> *)buildKeysForCurrentLayout {
if (self.layoutStyle == KBKeyboardLayoutStyleNumbers) {
// /3 +
NSArray *r1 = @[
[KBKey keyWithTitle:@"1" output:@"1"], [KBKey keyWithTitle:@"2" output:@"2"], [KBKey keyWithTitle:@"3" output:@"3"],
[KBKey keyWithTitle:@"4" output:@"4"], [KBKey keyWithTitle:@"5" output:@"5"], [KBKey keyWithTitle:@"6" output:@"6"],
[KBKey keyWithTitle:@"7" output:@"7"], [KBKey keyWithTitle:@"8" output:@"8"], [KBKey keyWithTitle:@"9" output:@"9"], [KBKey keyWithTitle:@"0" output:@"0"],
];
NSArray *r2 = @[
[KBKey keyWithTitle:@"-" output:@"-"], [KBKey keyWithTitle:@"/" output:@"/"], [KBKey keyWithTitle:@":" output:@":"],
[KBKey keyWithTitle:@";" output:@";"], [KBKey keyWithTitle:@"(" output:@"("], [KBKey keyWithTitle:@")" output:@")"],
[KBKey keyWithTitle:@"$" output:@"$"], [KBKey keyWithTitle:@"&" output:@"&"], [KBKey keyWithTitle:@"@" output:@"@"], [KBKey keyWithTitle:@"\"" output:@"\""],
];
NSArray *r3 = @[
[KBKey keyWithTitle:@"#+=" type:KBKeyTypeModeChange],
[KBKey keyWithTitle:@"," output:@","], [KBKey keyWithTitle:@"." output:@"."], [KBKey keyWithTitle:@"?" output:@"?"],
[KBKey keyWithTitle:@"!" output:@"!"], [KBKey keyWithTitle:@"'" output:@"'"],
[KBKey keyWithTitle:@"⌫" type:KBKeyTypeBackspace],
];
NSArray *r4 = @[
[KBKey keyWithTitle:@"ABC" type:KBKeyTypeModeChange],
[KBKey keyWithTitle:@"," output:@","],
[KBKey keyWithTitle:@"space" type:KBKeyTypeSpace],
[KBKey keyWithTitle:@"中/英" type:KBKeyTypeCustom],
[KBKey keyWithTitle:@"发送" type:KBKeyTypeReturn],
];
return @[r1, r2, r3, r4];
}
// QWERTY
NSArray *r1 = @[ @"Q", @"W", @"E", @"R", @"T", @"Y", @"U", @"I", @"O", @"P" ];
NSArray *r2 = @[ @"A", @"S", @"D", @"F", @"G", @"H", @"J", @"K", @"L" ];
NSArray *r3chars = @[ @"Z", @"X", @"C", @"V", @"B", @"N", @"M" ];
NSMutableArray *row1 = [NSMutableArray arrayWithCapacity:r1.count];
for (NSString *s in r1) { [row1 addObject:[KBKey keyWithTitle:s output:(self.shiftOn ? s : s.lowercaseString)]]; }
NSMutableArray *row2 = [NSMutableArray arrayWithCapacity:r2.count];
for (NSString *s in r2) { [row2 addObject:[KBKey keyWithTitle:s output:(self.shiftOn ? s : s.lowercaseString)]]; }
NSMutableArray *row3 = [NSMutableArray array];
[row3 addObject:[KBKey keyWithTitle:@"⇧" type:KBKeyTypeShift]];
for (NSString *s in r3chars) { [row3 addObject:[KBKey keyWithTitle:s output:(self.shiftOn ? s : s.lowercaseString)]]; }
[row3 addObject:[KBKey keyWithTitle:@"⌫" type:KBKeyTypeBackspace]];
NSArray *row4 = @[ [KBKey keyWithTitle:@"123" type:KBKeyTypeModeChange],
[KBKey keyWithTitle:@"," output:@","],
[KBKey keyWithTitle:@"space" type:KBKeyTypeSpace],
[KBKey keyWithTitle:@"中/英" type:KBKeyTypeCustom],
[KBKey keyWithTitle:@"发送" type:KBKeyTypeReturn] ];
return @[row1.copy, row2.copy, row3.copy, row4];
}
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys {
[self buildRow:row withKeys:keys edgeSpacerMultiplier:0.0];
}
- (void)buildRow:(UIView *)row withKeys:(NSArray<KBKey *> *)keys edgeSpacerMultiplier:(CGFloat)edgeSpacerMultiplier {
CGFloat hInset = 6; //
CGFloat spacing = 6; //
UIView *previous = nil;
UIView *leftSpacer = nil;
UIView *rightSpacer = nil;
if (edgeSpacerMultiplier > 0.0) {
leftSpacer = [UIView new];
rightSpacer = [UIView new];
leftSpacer.backgroundColor = [UIColor clearColor];
rightSpacer.backgroundColor = [UIColor clearColor];
[row addSubview:leftSpacer];
[row addSubview:rightSpacer];
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(row.mas_left).offset(hInset);
make.centerY.equalTo(row);
make.height.mas_equalTo(1);
}];
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(row.mas_right).offset(-hInset);
make.centerY.equalTo(row);
make.height.mas_equalTo(1);
}];
}
for (NSInteger i = 0; i < keys.count; i++) {
KBKey *key = keys[i];
KBKeyButton *btn = [[KBKeyButton alloc] init];
btn.key = key;
[btn setTitle:key.title forState:UIControlStateNormal];
[btn addTarget:self action:@selector(onKeyTapped:) forControlEvents:UIControlEventTouchUpInside];
[row addSubview:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.bottom.equalTo(row);
if (previous) {
make.left.equalTo(previous.mas_right).offset(spacing);
} else {
if (leftSpacer) {
make.left.equalTo(leftSpacer.mas_right).offset(spacing);
} else {
make.left.equalTo(row.mas_left).offset(hInset);
}
}
}];
//
if (key.type == KBKeyTypeCharacter) {
if (previous && previous != nil) {
if (((KBKeyButton *)previous).key.type == KBKeyTypeCharacter) {
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(previous);
}];
}
}
} else {
// special keys: give 1.5x of a character key by deferring constraint equalities after loop
}
previous = btn;
}
// 使
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
if (rightSpacer) {
make.right.equalTo(rightSpacer.mas_left).offset(-spacing);
} else {
make.right.equalTo(row.mas_right).offset(-hInset);
}
}];
//
KBKeyButton *firstChar = nil;
for (KBKeyButton *b in row.subviews) {
if ([b isKindOfClass:[KBKeyButton class]] && b.key.type == KBKeyTypeCharacter) { firstChar = b; break; }
}
if (firstChar) {
for (KBKeyButton *b in row.subviews) {
if (![b isKindOfClass:[KBKeyButton class]]) continue;
if (b.key.type == KBKeyTypeCharacter) continue;
CGFloat multiplier = 1.5;
if (b.key.type == KBKeyTypeSpace) multiplier = 4.0;
if (b.key.type == KBKeyTypeReturn) multiplier = 1.8;
if (b.key.type == KBKeyTypeModeChange || b.key.type == KBKeyTypeGlobe || b.key.type == KBKeyTypeShift || b.key.type == KBKeyTypeBackspace) {
multiplier = 1.5;
}
[b mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(firstChar).multipliedBy(multiplier);
}];
}
//
if (leftSpacer && rightSpacer) {
[leftSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
}];
[rightSpacer mas_makeConstraints:^(MASConstraintMaker *make) {
make.width.equalTo(firstChar).multipliedBy(edgeSpacerMultiplier);
}];
}
}
}
#pragma mark - Actions
- (void)onKeyTapped:(KBKeyButton *)sender {
KBKey *key = sender.key;
if (key.type == KBKeyTypeShift) {
self.shiftOn = !self.shiftOn;
[self reloadKeys];
return;
}
if ([self.delegate respondsToSelector:@selector(keyboardView:didTapKey:)]) {
[self.delegate keyboardView:self didTapKey:key];
}
}
#pragma mark - Lazy
- (UIView *)row1 { if (!_row1) _row1 = [UIView new]; return _row1; }
- (UIView *)row2 { if (!_row2) _row2 = [UIView new]; return _row2; }
- (UIView *)row3 { if (!_row3) _row3 = [UIView new]; return _row3; }
- (UIView *)row4 { if (!_row4) _row4 = [UIView new]; return _row4; }
@end

View File

@@ -9,8 +9,28 @@
NS_ASSUME_NONNULL_BEGIN
@class KBToolBar;
@protocol KBToolBarDelegate <NSObject>
@optional
/// 左侧 4 个功能按钮点击index: 0~3
- (void)toolBar:(KBToolBar *)toolBar didTapActionAtIndex:(NSInteger)index;
/// 右侧设置按钮点击
- (void)toolBarDidTapSettings:(KBToolBar *)toolBar;
@end
/// 顶部工具栏:左侧 4 个按钮,右侧 1 个设置按钮。
@interface KBToolBar : UIView
@property (nonatomic, weak, nullable) id<KBToolBarDelegate> delegate;
/// 左侧 4 个按钮的标题。默认值:@[@"Item1", @"Item2", @"Item3", @"Item4"]。
@property (nonatomic, copy) NSArray<NSString *> *leftButtonTitles;
/// 暴露按钮以便外部定制(只读;首次访问时懒加载创建)
@property (nonatomic, strong, readonly) NSArray<UIButton *> *leftButtons;
@property (nonatomic, strong, readonly) UIButton *settingsButton;
@end
NS_ASSUME_NONNULL_END

View File

@@ -7,15 +7,139 @@
#import "KBToolBar.h"
@interface KBToolBar ()
@property (nonatomic, strong) UIView *leftContainer;
@property (nonatomic, strong) NSArray<UIButton *> *leftButtonsInternal;
@property (nonatomic, strong) UIButton *settingsButtonInternal;
@end
@implementation KBToolBar
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
_leftButtonTitles = @[@"Item1", @"Item2", @"Item3", @"Item4"]; //
[self setupUI];
}
return self;
}
#pragma mark - Public
- (NSArray<UIButton *> *)leftButtons {
return self.leftButtonsInternal;
}
- (UIButton *)settingsButton {
return self.settingsButtonInternal;
}
- (void)setLeftButtonTitles:(NSArray<NSString *> *)leftButtonTitles {
_leftButtonTitles = [leftButtonTitles copy];
// Update titles if buttons already exist
[self.leftButtonsInternal enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (idx < self.leftButtonTitles.count) {
[obj setTitle:self.leftButtonTitles[idx] forState:UIControlStateNormal];
}
}];
}
#pragma mark -
- (void)setupUI {
[self addSubview:self.leftContainer];
[self addSubview:self.settingsButtonInternal];
//
[self.settingsButtonInternal mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.mas_right).offset(-12);
make.centerY.equalTo(self.mas_centerY);
make.width.height.mas_equalTo(32);
}];
//
[self.leftContainer mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.equalTo(self.mas_left).offset(12);
make.right.equalTo(self.settingsButtonInternal.mas_left).offset(-12);
make.centerY.equalTo(self.mas_centerY);
make.height.mas_equalTo(32);
}];
// 4
NSMutableArray<UIButton *> *buttons = [NSMutableArray arrayWithCapacity:4];
UIView *previous = nil;
for (NSInteger i = 0; i < 4; i++) {
UIButton *btn = [self buildActionButtonAtIndex:i];
[self.leftContainer addSubview:btn];
[buttons addObject:btn];
[btn mas_makeConstraints:^(MASConstraintMaker *make) {
if (previous) {
make.left.equalTo(previous.mas_right).offset(8);
make.width.equalTo(previous);
} else {
make.left.equalTo(self.leftContainer.mas_left);
}
make.top.bottom.equalTo(self.leftContainer);
}];
previous = btn;
}
//
[previous mas_makeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(self.leftContainer.mas_right);
}];
self.leftButtonsInternal = buttons.copy;
}
- (UIButton *)buildActionButtonAtIndex:(NSInteger)idx {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.layer.cornerRadius = 16;
btn.layer.masksToBounds = YES;
btn.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
btn.titleLabel.font = [UIFont systemFontOfSize:14 weight:UIFontWeightMedium];
NSString *title = (idx < self.leftButtonTitles.count) ? self.leftButtonTitles[idx] : [NSString stringWithFormat:@"Item%ld", (long)(idx+1)];
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
btn.tag = idx;
[btn addTarget:self action:@selector(onLeftAction:) forControlEvents:UIControlEventTouchUpInside];
return btn;
}
#pragma mark - Actions
- (void)onLeftAction:(UIButton *)sender {
if ([self.delegate respondsToSelector:@selector(toolBar:didTapActionAtIndex:)]) {
[self.delegate toolBar:self didTapActionAtIndex:sender.tag];
}
}
- (void)onSettings {
if ([self.delegate respondsToSelector:@selector(toolBarDidTapSettings:)]) {
[self.delegate toolBarDidTapSettings:self];
}
}
#pragma mark - Lazy
- (UIView *)leftContainer {
if (!_leftContainer) {
_leftContainer = [[UIView alloc] init];
_leftContainer.backgroundColor = [UIColor clearColor];
}
return _leftContainer;
}
- (UIButton *)settingsButtonInternal {
if (!_settingsButtonInternal) {
_settingsButtonInternal = [UIButton buttonWithType:UIButtonTypeSystem];
_settingsButtonInternal.layer.cornerRadius = 16;
_settingsButtonInternal.layer.masksToBounds = YES;
_settingsButtonInternal.backgroundColor = [UIColor colorWithWhite:1 alpha:0.9];
[_settingsButtonInternal setTitle:@"⚙︎" forState:UIControlStateNormal]; // 齿
[_settingsButtonInternal setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
[_settingsButtonInternal addTarget:self action:@selector(onSettings) forControlEvents:UIControlEventTouchUpInside];
}
return _settingsButtonInternal;
}
@end

View File

@@ -20,5 +20,5 @@ target 'keyBoard' do
pod 'MJExtension', '3.4.2'
pod 'MJRefresh', '3.7.9'
pod 'SDWebImage', '5.21.1'
pod 'LookinServer', :configurations => ['Debug']
end

View File

@@ -15,6 +15,9 @@ PODS:
- AFNetworking/UIKit (4.0.1):
- AFNetworking/NSURLSession
- Bugly (2.6.1)
- LookinServer (1.2.8):
- LookinServer/Core (= 1.2.8)
- LookinServer/Core (1.2.8)
- Masonry (1.1.0)
- MJExtension (3.4.2)
- MJRefresh (3.7.9)
@@ -25,6 +28,7 @@ PODS:
DEPENDENCIES:
- AFNetworking (= 4.0.1)
- Bugly (= 2.6.1)
- LookinServer
- Masonry (= 1.1.0)
- MJExtension (= 3.4.2)
- MJRefresh (= 3.7.9)
@@ -34,6 +38,7 @@ SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- AFNetworking
- Bugly
- LookinServer
- Masonry
- MJExtension
- MJRefresh
@@ -42,11 +47,12 @@ SPEC REPOS:
SPEC CHECKSUMS:
AFNetworking: 3bd23d814e976cd148d7d44c3ab78017b744cd58
Bugly: 217ac2ce5f0f2626d43dbaa4f70764c953a26a31
LookinServer: 1b2b61c6402ae29fa22182d48f5cd067b4e99e80
Masonry: 678fab65091a9290e40e2832a55e7ab731aad201
MJExtension: e97d164cb411aa9795cf576093a1fa208b4a8dd8
MJRefresh: ff9e531227924c84ce459338414550a05d2aea78
SDWebImage: f29024626962457f3470184232766516dee8dfea
PODFILE CHECKSUM: b3c72fe500149c35040cdd73c1d91fe05777bc5f
PODFILE CHECKSUM: 4f2fbf9a7c2f24c74f9f26aba9933db3ee43ff84
COCOAPODS: 1.16.2

21
Pods/LookinServer/LICENSE generated Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) [2023] [LI KAI]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

73
Pods/LookinServer/README.md generated Normal file
View File

@@ -0,0 +1,73 @@
![Preview](https://cdn.lookin.work/public/style/images/independent/homepage/preview_en_1x.jpg "Preview")
# Introduction
You can inspect and modify views in iOS app via Lookin, just like UI Inspector in Xcode, or another app called Reveal.
Official Websitehttps://lookin.work/
# Integration Guide
To use Lookin macOS app, you need to integrate LookinServer (iOS Framework of Lookin) into your iOS project.
> **Warning**
> 1. Never integrate LookinServer in Release building configuration.
> 2. Do not use versions earlier than 1.0.6, as it contains a critical bug that could lead to online incidents in your project: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab
## via CocoaPods:
### Swift Project
`pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']`
### Objective-C Project
`pod 'LookinServer', :configurations => ['Debug']`
## via Swift Package Manager:
`https://github.com/QMUI/LookinServer/`
# Repository
LookinServer: https://github.com/QMUI/LookinServer
macOS app: https://github.com/hughkli/Lookin/
# Tips
- How to display custom information in Lookin: https://bytedance.larkoffice.com/docx/TRridRXeUoErMTxs94bcnGchnlb
- How to display more member variables in Lookin: https://bytedance.larkoffice.com/docx/CKRndHqdeoub11xSqUZcMlFhnWe
- How to turn on Swift optimization for Lookin: https://bytedance.larkoffice.com/docx/GFRLdzpeKoakeyxvwgCcZ5XdnTb
- Documentation Collection: https://bytedance.larkoffice.com/docx/Yvv1d57XQoe5l0xZ0ZRc0ILfnWb
# Acknowledgements
https://qxh1ndiez2w.feishu.cn/docx/YIFjdE4gIolp3hxn1tGckiBxnWf
---
# 简介
Lookin 可以查看与修改 iOS App 里的 UI 对象,类似于 Xcode 自带的 UI Inspector 工具,或另一款叫做 Reveal 的软件。
官网https://lookin.work/
# 安装 LookinServer Framework
如果这是你的 iOS 项目第一次使用 Lookin则需要先把 LookinServer 这款 iOS Framework 集成到你的 iOS 项目中。
> **Warning**
>
> 1. 不要在 AppStore 模式下集成 LookinServer。
> 2. 不要使用早于 1.0.6 的版本,因为它包含一个严重 Bug可能导致线上事故: https://qxh1ndiez2w.feishu.cn/wiki/Z9SpwT7zWiqvYvkBe7Lc6Disnab
## 通过 CocoaPods
### Swift 项目
`pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug']`
### Objective-C 项目
`pod 'LookinServer', :configurations => ['Debug']`
## 通过 Swift Package Manager:
`https://github.com/QMUI/LookinServer/`
# 源代码仓库
iOS 端 LookinServerhttps://github.com/QMUI/LookinServer
macOS 端软件https://github.com/hughkli/Lookin/
# 技巧
- 如何在 Lookin 中展示自定义信息: https://bytedance.larkoffice.com/docx/TRridRXeUoErMTxs94bcnGchnlb
- 如何在 Lookin 中展示更多成员变量: https://bytedance.larkoffice.com/docx/CKRndHqdeoub11xSqUZcMlFhnWe
- 如何为 Lookin 开启 Swift 优化: https://bytedance.larkoffice.com/docx/GFRLdzpeKoakeyxvwgCcZ5XdnTb
- 文档汇总https://bytedance.larkoffice.com/docx/Yvv1d57XQoe5l0xZ0ZRc0ILfnWb
# 鸣谢
https://qxh1ndiez2w.feishu.cn/docx/YIFjdE4gIolp3hxn1tGckiBxnWf

View File

@@ -0,0 +1,40 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinIvarTrace.h
// Lookin
//
// Created by Li Kai on 2019/4/30.
// https://lookin.work
//
#import <Foundation/Foundation.h>
extern NSString *const LookinIvarTraceRelationValue_Self;
/// 如果 hostClassName 和 ivarName 均 equal则认为两个 LookinIvarTrace 对象彼此 equal
/// 比如 A 是 B 的 superview且 A 的 "_stageView" 指向 B则 B 会有一个 LookinIvarTracehostType 为 “superview”hostClassName 为 A 的 classivarName 为 “_stageView”
@interface LookinIvarTrace : NSObject <NSSecureCoding, NSCopying>
/// 该值可能是 "superview"、"superlayer"、“self” 或 nil
@property(nonatomic, copy) NSString *relation;
@property(nonatomic, copy) NSString *hostClassName;
@property(nonatomic, copy) NSString *ivarName;
#pragma mark - No Coding
#if TARGET_OS_IPHONE
@property(nonatomic, weak) id hostObject;
#endif
@end
@interface NSObject (LookinServerTrace)
@property(nonatomic, copy) NSArray<LookinIvarTrace *> *lks_ivarTraces;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,70 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinIvarTrace.m
// Lookin
//
// Created by Li Kai on 2019/4/30.
// https://lookin.work
//
#import "LookinIvarTrace.h"
NSString *const LookinIvarTraceRelationValue_Self = @"self";
@implementation LookinIvarTrace
#pragma mark - Equal
- (NSUInteger)hash {
return self.hostClassName.hash ^ self.ivarName.hash;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinIvarTrace class]]) {
return NO;
}
LookinIvarTrace *comparedObj = object;
if ([self.hostClassName isEqualToString:comparedObj.hostClassName] && [self.ivarName isEqualToString:comparedObj.ivarName]) {
return YES;
}
return NO;
}
#pragma mark - <NSCopying>
- (id)copyWithZone:(NSZone *)zone {
LookinIvarTrace *newTrace = [[LookinIvarTrace allocWithZone:zone] init];
newTrace.relation = self.relation;
newTrace.hostClassName = self.hostClassName;
newTrace.ivarName = self.ivarName;
return newTrace;
}
#pragma mark - <NSCoding>
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.relation forKey:@"relation"];
[aCoder encodeObject:self.hostClassName forKey:@"hostClassName"];
[aCoder encodeObject:self.ivarName forKey:@"ivarName"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.relation = [aDecoder decodeObjectForKey:@"relation"];
self.hostClassName = [aDecoder decodeObjectForKey:@"hostClassName"];
self.ivarName = [aDecoder decodeObjectForKey:@"ivarName"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,41 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinMobile.h
// WeRead
//
// Created by Li Kai on 2018/11/30.
// Copyright © 2018 tencent. All rights reserved.
//
#import "LookinDefines.h"
#import "TargetConditionals.h"
#import <UIKit/UIKit.h>
@interface CALayer (LookinServer)
/// 如果 myView.layer == myLayer则 myLayer.lks_hostView 会返回 myView
@property(nonatomic, readonly, weak) UIView *lks_hostView;
- (UIWindow *)lks_window;
- (CGRect)lks_frameInWindow:(UIWindow *)window;
- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality;
/// 当没有 sublayers 时,该方法返回 nil
- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality;
/// 获取和该对象有关的对象的 Class 层级树
- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList;
- (NSArray<NSString *> *)lks_selfRelation;
@property(nonatomic, strong) UIColor *lks_backgroundColor;
@property(nonatomic, strong) UIColor *lks_borderColor;
@property(nonatomic, strong) UIColor *lks_shadowColor;
@property(nonatomic, assign) CGFloat lks_shadowOffsetWidth;
@property(nonatomic, assign) CGFloat lks_shadowOffsetHeight;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,233 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinMobile.m
// WeRead
//
// Created by Li Kai on 2018/11/30.
// Copyright © 2018 tencent. All rights reserved.
//
#import "CALayer+LookinServer.h"
#import "LKS_HierarchyDisplayItemsMaker.h"
#import "LookinDisplayItem.h"
#import <objc/runtime.h>
#import "LKS_ConnectionManager.h"
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "UIColor+LookinServer.h"
#import "LKS_MultiplatformAdapter.h"
@implementation CALayer (LookinServer)
- (UIWindow *)lks_window {
CALayer *layer = self;
while (layer) {
UIView *hostView = layer.lks_hostView;
if (hostView.window) {
return hostView.window;
} else if ([hostView isKindOfClass:[UIWindow class]]) {
return (UIWindow *)hostView;
}
layer = layer.superlayer;
}
return nil;
}
- (CGRect)lks_frameInWindow:(UIWindow *)window {
UIWindow *selfWindow = [self lks_window];
if (!selfWindow) {
return CGRectZero;
}
CGRect rectInSelfWindow = [selfWindow.layer convertRect:self.frame fromLayer:self.superlayer];
CGRect rectInWindow = [window convertRect:rectInSelfWindow fromWindow:selfWindow];
return rectInWindow;
}
#pragma mark - Host View
- (UIView *)lks_hostView {
if (self.delegate && [self.delegate isKindOfClass:UIView.class]) {
UIView *view = (UIView *)self.delegate;
if (view.layer == self) {
return view;
}
}
return nil;
}
#pragma mark - Screenshot
- (UIImage *)lks_groupScreenshotWithLowQuality:(BOOL)lowQuality {
CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
CGFloat pixelWidth = self.frame.size.width * screenScale;
CGFloat pixelHeight = self.frame.size.height * screenScale;
if (pixelWidth <= 0 || pixelHeight <= 0) {
return nil;
}
CGFloat renderScale = lowQuality ? 1 : 0;
CGFloat maxLength = MAX(pixelWidth, pixelHeight);
if (maxLength > LookinNodeImageMaxLengthInPx) {
// LookinNodeImageMaxLengthInPx
// renderScale 1 1 1
renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
}
CGSize contextSize = self.frame.size;
if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
return nil;
}
UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
[self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
} else {
[self renderInContext:context];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
- (UIImage *)lks_soloScreenshotWithLowQuality:(BOOL)lowQuality {
if (!self.sublayers.count) {
return nil;
}
CGFloat screenScale = [LKS_MultiplatformAdapter mainScreenScale];
CGFloat pixelWidth = self.frame.size.width * screenScale;
CGFloat pixelHeight = self.frame.size.height * screenScale;
if (pixelWidth <= 0 || pixelHeight <= 0) {
return nil;
}
CGFloat renderScale = lowQuality ? 1 : 0;
CGFloat maxLength = MAX(pixelWidth, pixelHeight);
if (maxLength > LookinNodeImageMaxLengthInPx) {
// LookinNodeImageMaxLengthInPx
// renderScale 1 1 1
renderScale = MIN(screenScale * LookinNodeImageMaxLengthInPx / maxLength, 1);
}
if (self.sublayers.count) {
NSArray<CALayer *> *sublayers = [self.sublayers copy];
NSMutableArray<CALayer *> *visibleSublayers = [NSMutableArray arrayWithCapacity:sublayers.count];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
if (!sublayer.hidden) {
sublayer.hidden = YES;
[visibleSublayers addObject:sublayer];
}
}];
CGSize contextSize = self.frame.size;
if (contextSize.width <= 0 || contextSize.height <= 0 || contextSize.width > 20000 || contextSize.height > 20000) {
NSLog(@"LookinServer - Failed to capture screenshot. Invalid context size: %@ x %@", @(contextSize.width), @(contextSize.height));
return nil;
}
UIGraphicsBeginImageContextWithOptions(contextSize, NO, renderScale);
CGContextRef context = UIGraphicsGetCurrentContext();
if (self.lks_hostView && !self.lks_hostView.lks_isChildrenViewOfTabBar) {
[self.lks_hostView drawViewHierarchyInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height) afterScreenUpdates:YES];
} else {
[self renderInContext:context];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[visibleSublayers enumerateObjectsUsingBlock:^(CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
sublayer.hidden = NO;
}];
return image;
}
return nil;
}
- (NSArray<NSArray<NSString *> *> *)lks_relatedClassChainList {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:2];
if (self.lks_hostView) {
[array addObject:[CALayer lks_getClassListOfObject:self.lks_hostView endingClass:@"UIView"]];
UIViewController* vc = [self.lks_hostView lks_findHostViewController];
if (vc) {
[array addObject:[CALayer lks_getClassListOfObject:vc endingClass:@"UIViewController"]];
}
} else {
[array addObject:[CALayer lks_getClassListOfObject:self endingClass:@"CALayer"]];
}
return array.copy;
}
+ (NSArray<NSString *> *)lks_getClassListOfObject:(id)object endingClass:(NSString *)endingClass {
NSArray<NSString *> *completedList = [object lks_classChainList];
NSUInteger endingIdx = [completedList indexOfObject:endingClass];
if (endingIdx != NSNotFound) {
completedList = [completedList subarrayWithRange:NSMakeRange(0, endingIdx + 1)];
}
return completedList;
}
- (NSArray<NSString *> *)lks_selfRelation {
NSMutableArray *array = [NSMutableArray array];
NSMutableArray<LookinIvarTrace *> *ivarTraces = [NSMutableArray array];
if (self.lks_hostView) {
UIViewController* vc = [self.lks_hostView lks_findHostViewController];
if (vc) {
[array addObject:[NSString stringWithFormat:@"(%@ *).view", NSStringFromClass(vc.class)]];
[ivarTraces addObjectsFromArray:vc.lks_ivarTraces];
}
[ivarTraces addObjectsFromArray:self.lks_hostView.lks_ivarTraces];
} else {
[ivarTraces addObjectsFromArray:self.lks_ivarTraces];
}
if (ivarTraces.count) {
[array addObjectsFromArray:[ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *value) {
return [NSString stringWithFormat:@"(%@ *) -> %@", value.hostClassName, value.ivarName];
}]];
}
return array.count ? array.copy : nil;
}
- (UIColor *)lks_backgroundColor {
return [UIColor lks_colorWithCGColor:self.backgroundColor];
}
- (void)setLks_backgroundColor:(UIColor *)lks_backgroundColor {
self.backgroundColor = lks_backgroundColor.CGColor;
}
- (UIColor *)lks_borderColor {
return [UIColor lks_colorWithCGColor:self.borderColor];
}
- (void)setLks_borderColor:(UIColor *)lks_borderColor {
self.borderColor = lks_borderColor.CGColor;
}
- (UIColor *)lks_shadowColor {
return [UIColor lks_colorWithCGColor:self.shadowColor];
}
- (void)setLks_shadowColor:(UIColor *)lks_shadowColor {
self.shadowColor = lks_shadowColor.CGColor;
}
- (CGFloat)lks_shadowOffsetWidth {
return self.shadowOffset.width;
}
- (void)setLks_shadowOffsetWidth:(CGFloat)lks_shadowOffsetWidth {
self.shadowOffset = CGSizeMake(lks_shadowOffsetWidth, self.shadowOffset.height);
}
- (CGFloat)lks_shadowOffsetHeight {
return self.shadowOffset.height;
}
- (void)setLks_shadowOffsetHeight:(CGFloat)lks_shadowOffsetHeight {
self.shadowOffset = CGSizeMake(self.shadowOffset.width, lks_shadowOffsetHeight);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,41 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
@class LookinIvarTrace;
@interface NSObject (LookinServer)
#pragma mark - oid
/// 如果 oid 不存在则会创建新的 oid
- (unsigned long)lks_registerOid;
/// 0 表示不存在
@property(nonatomic, assign) unsigned long lks_oid;
+ (NSObject *)lks_objectWithOid:(unsigned long)oid;
#pragma mark - trace
@property(nonatomic, copy) NSString *lks_specialTrace;
+ (void)lks_clearAllObjectsTraces;
/**
获取当前对象的 Class 层级树,如 @[@"UIView", @"UIResponder", @"NSObject"]。未 demangle有 Swift Module Name
*/
- (NSArray<NSString *> *)lks_classChainList;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,99 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "NSObject+Lookin.h"
#import "NSObject+LookinServer.h"
#import "LookinServerDefines.h"
#import "LKS_ObjectRegistry.h"
#import <objc/runtime.h>
@implementation NSObject (LookinServer)
#pragma mark - oid
- (unsigned long)lks_registerOid {
if (!self.lks_oid) {
unsigned long oid = [[LKS_ObjectRegistry sharedInstance] addObject:self];
self.lks_oid = oid;
}
return self.lks_oid;
}
- (void)setLks_oid:(unsigned long)lks_oid {
[self lookin_bindObject:@(lks_oid) forKey:@"lks_oid"];
}
- (unsigned long)lks_oid {
NSNumber *number = [self lookin_getBindObjectForKey:@"lks_oid"];
return [number unsignedLongValue];
}
+ (NSObject *)lks_objectWithOid:(unsigned long)oid {
return [[LKS_ObjectRegistry sharedInstance] objectWithOid:oid];
}
#pragma mark - trace
- (void)setLks_ivarTraces:(NSArray<LookinIvarTrace *> *)lks_ivarTraces {
[self lookin_bindObject:lks_ivarTraces.copy forKey:@"lks_ivarTraces"];
if (lks_ivarTraces) {
[[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
}
}
- (NSArray<LookinIvarTrace *> *)lks_ivarTraces {
return [self lookin_getBindObjectForKey:@"lks_ivarTraces"];
}
- (void)setLks_specialTrace:(NSString *)lks_specialTrace {
[self lookin_bindObject:lks_specialTrace forKey:@"lks_specialTrace"];
if (lks_specialTrace) {
[[NSObject lks_allObjectsWithTraces] addPointer:(void *)self];
}
}
- (NSString *)lks_specialTrace {
return [self lookin_getBindObjectForKey:@"lks_specialTrace"];
}
+ (void)lks_clearAllObjectsTraces {
[[[NSObject lks_allObjectsWithTraces] allObjects] enumerateObjectsUsingBlock:^(NSObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
obj.lks_ivarTraces = nil;
obj.lks_specialTrace = nil;
}];
[NSObject lks_allObjectsWithTraces].count = 0;
}
+ (NSPointerArray *)lks_allObjectsWithTraces {
static dispatch_once_t onceToken;
static NSPointerArray *lks_allObjectsWithTraces = nil;
dispatch_once(&onceToken,^{
lks_allObjectsWithTraces = [NSPointerArray weakObjectsPointerArray];
});
return lks_allObjectsWithTraces;
}
- (NSArray<NSString *> *)lks_classChainList {
NSMutableArray<NSString *> *classChainList = [NSMutableArray array];
Class currentClass = self.class;
while (currentClass) {
NSString *currentClassName = NSStringFromClass(currentClass);
if (currentClassName) {
[classChainList addObject:currentClassName];
}
currentClass = [currentClass superclass];
}
return classChainList.copy;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIBlurEffect+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIBlurEffect (LookinServer)
/// 该 number 包装的对象是 UIBlurEffectStyle之所以用 NSNumber 是因为想把 0 和 nil 区分开,毕竟这里是在 hook 系统,稳一点好。
/// 该方法的实现需要 Hook因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
@property(nonatomic, strong) NSNumber *lks_effectStyleNumber;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,57 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIBlurEffect+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import "UIBlurEffect+LookinServer.h"
#import "NSObject+Lookin.h"
#import <objc/runtime.h>
@implementation UIBlurEffect (LookinServer)
#ifdef LOOKIN_SERVER_DISABLE_HOOK
- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
}
- (NSNumber *)lks_effectStyleNumber {
return nil;
}
#else
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getClassMethod([self class], @selector(effectWithStyle:));
Method newMethod = class_getClassMethod([self class], @selector(lks_effectWithStyle:));
method_exchangeImplementations(oriMethod, newMethod);
});
}
+ (UIBlurEffect *)lks_effectWithStyle:(UIBlurEffectStyle)style {
id effect = [self lks_effectWithStyle:style];
if ([effect respondsToSelector:@selector(setLks_effectStyleNumber:)]) {
[effect setLks_effectStyleNumber:@(style)];
}
return effect;
}
- (void)setLks_effectStyleNumber:(NSNumber *)lks_effectStyleNumber {
[self lookin_bindObject:lks_effectStyleNumber forKey:@"lks_effectStyleNumber"];
}
- (NSNumber *)lks_effectStyleNumber {
return [self lookin_getBindObjectForKey:@"lks_effectStyleNumber"];
}
#endif /* LOOKIN_SERVER_DISABLE_HOOK */
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIColor+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/6/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIColor (LookinServer)
- (NSArray<NSNumber *> *)lks_rgbaComponents;
+ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components;
- (NSString *)lks_rgbaString;
- (NSString *)lks_hexString;
/// will check if the argument is a real CGColor
+ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,183 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIColor+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/6/5.
// https://lookin.work
//
#import "UIColor+LookinServer.h"
@implementation UIColor (LookinServer)
- (NSArray<NSNumber *> *)lks_rgbaComponents {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else if (CGColorGetNumberOfComponents(cgColor) == 1) {
r = components[0];
g = components[0];
b = components[0];
a = components[0];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
}
+ (instancetype)lks_colorFromRGBAComponents:(NSArray<NSNumber *> *)components {
if (!components) {
return nil;
}
if (components.count != 4) {
NSAssert(NO, @"");
return nil;
}
UIColor *color = [UIColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue];
return color;
}
- (NSString *)lks_rgbaString {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
if (a >= 1) {
return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255];
} else {
return [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %.2f)", r * 255, g * 255, b * 255, a];
}
}
- (NSString *)lks_hexString {
CGFloat r, g, b, a;
CGColorRef cgColor = [self CGColor];
const CGFloat *components = CGColorGetComponents(cgColor);
if (CGColorGetNumberOfComponents(cgColor) == 4) {
r = components[0];
g = components[1];
b = components[2];
a = components[3];
} else if (CGColorGetNumberOfComponents(cgColor) == 2) {
r = components[0];
g = components[0];
b = components[0];
a = components[1];
} else {
r = 0;
g = 0;
b = 0;
a = 0;
NSAssert(NO, @"");
}
NSInteger red = r * 255;
NSInteger green = g * 255;
NSInteger blue = b * 255;
NSInteger alpha = a * 255;
return [[NSString stringWithFormat:@"#%@%@%@%@",
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:alpha]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:red]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:green]],
[UIColor _alignColorHexStringLength:[UIColor _hexStringWithInteger:blue]]] lowercaseString];
}
// 0F0F
+ (NSString *)_alignColorHexStringLength:(NSString *)hexString {
return hexString.length < 2 ? [@"0" stringByAppendingString:hexString] : hexString;
}
+ (NSString *)_hexStringWithInteger:(NSInteger)integer {
NSString *hexString = @"";
NSInteger remainder = 0;
for (NSInteger i = 0; i < 9; i++) {
remainder = integer % 16;
integer = integer / 16;
NSString *letter = [self _hexLetterStringWithInteger:remainder];
hexString = [letter stringByAppendingString:hexString];
if (integer == 0) {
break;
}
}
return hexString;
}
+ (NSString *)_hexLetterStringWithInteger:(NSInteger)integer {
NSAssert(integer < 16, @"要转换的数必须是16进制里的个位数也即小于16但你传给我是%@", @(integer));
NSString *letter = nil;
switch (integer) {
case 10:
letter = @"A";
break;
case 11:
letter = @"B";
break;
case 12:
letter = @"C";
break;
case 13:
letter = @"D";
break;
case 14:
letter = @"E";
break;
case 15:
letter = @"F";
break;
default:
letter = [[NSString alloc]initWithFormat:@"%@", @(integer)];
break;
}
return letter;
}
+ (UIColor *)lks_colorWithCGColor:(CGColorRef)cgColor {
if (!cgColor) {
return nil;
}
if (CFGetTypeID(cgColor) != CGColorGetTypeID()) {
return nil;
}
return [UIColor colorWithCGColor:cgColor];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,22 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImage+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/5/14.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIImage (LookinServer)
/// 该方法的实现需要 Hook因此若定义了 LOOKIN_SERVER_DISABLE_HOOK 宏,则属性会返回 nil
@property(nonatomic, copy) NSString *lks_imageSourceName;
- (NSData *)lookin_data;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,95 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImage+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/5/14.
// https://lookin.work
//
#import <objc/runtime.h>
#import "UIImage+LookinServer.h"
#import "LookinServerDefines.h"
@implementation UIImage (LookinServer)
#ifdef LOOKIN_SERVER_DISABLE_HOOK
- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
}
- (NSString *)lks_imageSourceName {
return nil;
}
#else
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method oriMethod = class_getClassMethod([self class], @selector(imageNamed:));
Method newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:));
method_exchangeImplementations(oriMethod, newMethod);
oriMethod = class_getClassMethod([self class], @selector(imageWithContentsOfFile:));
newMethod = class_getClassMethod([self class], @selector(lks_imageWithContentsOfFile:));
method_exchangeImplementations(oriMethod, newMethod);
oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:compatibleWithTraitCollection:));
newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:compatibleWithTraitCollection:));
method_exchangeImplementations(oriMethod, newMethod);
if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
oriMethod = class_getClassMethod([self class], @selector(imageNamed:inBundle:withConfiguration:));
newMethod = class_getClassMethod([self class], @selector(lks_imageNamed:inBundle:withConfiguration:));
method_exchangeImplementations(oriMethod, newMethod);
}
});
}
+ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle withConfiguration:(nullable UIImageConfiguration *)configuration API_AVAILABLE(ios(13.0),tvos(13.0),watchos(6.0))
{
UIImage *image = [self lks_imageNamed:name inBundle:bundle withConfiguration:configuration];
image.lks_imageSourceName = name;
return image;
}
+ (nullable UIImage *)lks_imageNamed:(NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection API_AVAILABLE(ios(8.0))
{
UIImage *image = [self lks_imageNamed:name inBundle:bundle compatibleWithTraitCollection:traitCollection];
image.lks_imageSourceName = name;
return image;
}
+ (UIImage *)lks_imageNamed:(NSString *)name {
UIImage *image = [self lks_imageNamed:name];
image.lks_imageSourceName = name;
return image;
}
+ (UIImage *)lks_imageWithContentsOfFile:(NSString *)path {
UIImage *image = [self lks_imageWithContentsOfFile:path];
NSString *fileName = [[path componentsSeparatedByString:@"/"].lastObject componentsSeparatedByString:@"."].firstObject;
image.lks_imageSourceName = fileName;
return image;
}
- (void)setLks_imageSourceName:(NSString *)lks_imageSourceName {
[self lookin_bindObject:lks_imageSourceName.copy forKey:@"lks_imageSourceName"];
}
- (NSString *)lks_imageSourceName {
return [self lookin_getBindObjectForKey:@"lks_imageSourceName"];
}
#endif /* LOOKIN_SERVER_DISABLE_HOOK */
- (NSData *)lookin_data {
return UIImagePNGRepresentation(self);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,20 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImageView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIImageView (LookinServer)
- (NSString *)lks_imageSourceName;
- (NSNumber *)lks_imageViewOidIfHasImage;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIImageView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import "UIImageView+LookinServer.h"
#import "UIImage+LookinServer.h"
#import "NSObject+LookinServer.h"
@implementation UIImageView (LookinServer)
- (NSString *)lks_imageSourceName {
return self.image.lks_imageSourceName;
}
- (NSNumber *)lks_imageViewOidIfHasImage {
if (!self.image) {
return nil;
}
unsigned long oid = [self lks_registerOid];
return @(oid);
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UILabel+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UILabel (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UILabel+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UILabel+LookinServer.h"
@implementation UILabel (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITableView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/9/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITableView (LookinServer)
- (NSArray<NSNumber *> *)lks_numberOfRows;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITableView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/9/5.
// https://lookin.work
//
#import "UITableView+LookinServer.h"
#import "LookinServerDefines.h"
@implementation UITableView (LookinServer)
- (NSArray<NSNumber *> *)lks_numberOfRows {
NSUInteger sectionsCount = MIN(self.numberOfSections, 10);
NSArray<NSNumber *> *rowsCount = [NSArray lookin_arrayWithCount:sectionsCount block:^id(NSUInteger idx) {
return @([self numberOfRowsInSection:idx]);
}];
if (rowsCount.count == 0) {
return nil;
}
return rowsCount;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextField+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITextField (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextField+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UITextField+LookinServer.h"
@implementation UITextField (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UITextView (LookinServer)
@property(nonatomic, assign) CGFloat lks_fontSize;
- (NSString *)lks_fontName;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UITextView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/2/26.
// https://lookin.work
//
#import "UITextView+LookinServer.h"
@implementation UITextView (LookinServer)
- (CGFloat)lks_fontSize {
return self.font.pointSize;
}
- (void)setLks_fontSize:(CGFloat)lks_fontSize {
UIFont *font = [self.font fontWithSize:lks_fontSize];
self.font = font;
}
- (NSString *)lks_fontName {
return self.font.fontName;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,44 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/3/19.
// https://lookin.work
//
#import "LookinDefines.h"
#import <UIKit/UIKit.h>
@interface UIView (LookinServer)
/// 如果 myViewController.view = myView则可以通过 myView 的 lks_findHostViewController 方法找到 myViewController
- (UIViewController *)lks_findHostViewController;
/// 是否是 UITabBar 的 childrenView如果是的话则截图时需要强制使用 renderInContext: 的方式而非 drawViewHierarchyInRect:afterScreenUpdates: 否则在 iOS 13 上获取到的图像是空的不知道为什么
@property(nonatomic, assign) BOOL lks_isChildrenViewOfTabBar;
/// point 是相对于 receiver 自身的坐标系
- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses;
- (CGFloat)lks_bestWidth;
- (CGFloat)lks_bestHeight;
- (CGSize)lks_bestSize;
@property(nonatomic, assign) float lks_horizontalContentHuggingPriority;
@property(nonatomic, assign) float lks_verticalContentHuggingPriority;
@property(nonatomic, assign) float lks_horizontalContentCompressionResistancePriority;
@property(nonatomic, assign) float lks_verticalContentCompressionResistancePriority;
/// 遍历全局的 view 并给他们设置 lks_involvedRawConstraints 属性
+ (void)lks_rebuildGlobalInvolvedRawConstraints;
/// 该属性保存了牵扯到当前 view 的所有 constraints包括那些没有生效的
@property(nonatomic, strong) NSMutableArray<NSLayoutConstraint *> *lks_involvedRawConstraints;
- (NSArray<NSDictionary<NSString *, id> *> *)lks_constraints;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,215 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/3/19.
// https://lookin.work
//
#import "UIView+LookinServer.h"
#import <objc/runtime.h>
#import "LookinObject.h"
#import "LookinAutoLayoutConstraint.h"
#import "LookinServerDefines.h"
#import "LKS_MultiplatformAdapter.h"
@implementation UIView (LookinServer)
- (UIViewController *)lks_findHostViewController {
UIResponder *responder = [self nextResponder];
if (!responder) {
return nil;
}
if (![responder isKindOfClass:[UIViewController class]]) {
return nil;
}
UIViewController *viewController = (UIViewController *)responder;
if (viewController.view != self) {
return nil;
}
return viewController;
}
- (UIView *)lks_subviewAtPoint:(CGPoint)point preferredClasses:(NSArray<Class> *)preferredClasses {
BOOL isPreferredClassForSelf = [preferredClasses lookin_any:^BOOL(Class obj) {
return [self isKindOfClass:obj];
}];
if (isPreferredClassForSelf) {
return self;
}
UIView *targetView = [self.subviews lookin_lastFiltered:^BOOL(__kindof UIView *obj) {
if (obj.hidden || obj.alpha <= 0.01) {
return NO;
}
BOOL contains = CGRectContainsPoint(obj.frame, point);
return contains;
}];
if (!targetView) {
return self;
}
CGPoint newPoint = [targetView convertPoint:point fromView:self];
targetView = [targetView lks_subviewAtPoint:newPoint preferredClasses:preferredClasses];
return targetView;
}
- (CGSize)lks_bestSize {
return [self sizeThatFits:CGSizeMake(CGFLOAT_MAX, CGFLOAT_MAX)];
}
- (CGFloat)lks_bestWidth {
return self.lks_bestSize.width;
}
- (CGFloat)lks_bestHeight {
return self.lks_bestSize.height;
}
- (void)setLks_isChildrenViewOfTabBar:(BOOL)lks_isChildrenViewOfTabBar {
[self lookin_bindBOOL:lks_isChildrenViewOfTabBar forKey:@"lks_isChildrenViewOfTabBar"];
}
- (BOOL)lks_isChildrenViewOfTabBar {
return [self lookin_getBindBOOLForKey:@"lks_isChildrenViewOfTabBar"];
}
- (void)setLks_verticalContentHuggingPriority:(float)lks_verticalContentHuggingPriority {
[self setContentHuggingPriority:lks_verticalContentHuggingPriority forAxis:UILayoutConstraintAxisVertical];
}
- (float)lks_verticalContentHuggingPriority {
return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisVertical];
}
- (void)setLks_horizontalContentHuggingPriority:(float)lks_horizontalContentHuggingPriority {
[self setContentHuggingPriority:lks_horizontalContentHuggingPriority forAxis:UILayoutConstraintAxisHorizontal];
}
- (float)lks_horizontalContentHuggingPriority {
return [self contentHuggingPriorityForAxis:UILayoutConstraintAxisHorizontal];
}
- (void)setLks_verticalContentCompressionResistancePriority:(float)lks_verticalContentCompressionResistancePriority {
[self setContentCompressionResistancePriority:lks_verticalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisVertical];
}
- (float)lks_verticalContentCompressionResistancePriority {
return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisVertical];
}
- (void)setLks_horizontalContentCompressionResistancePriority:(float)lks_horizontalContentCompressionResistancePriority {
[self setContentCompressionResistancePriority:lks_horizontalContentCompressionResistancePriority forAxis:UILayoutConstraintAxisHorizontal];
}
- (float)lks_horizontalContentCompressionResistancePriority {
return [self contentCompressionResistancePriorityForAxis:UILayoutConstraintAxisHorizontal];
}
+ (void)lks_rebuildGlobalInvolvedRawConstraints {
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_removeInvolvedRawConstraintsForViewsRootedByView:window];
}];
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_addInvolvedRawConstraintsForViewsRootedByView:window];
}];
}
+ (void)lks_addInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
[rootView.constraints enumerateObjectsUsingBlock:^(__kindof NSLayoutConstraint * _Nonnull constraint, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *firstView = constraint.firstItem;
if ([firstView isKindOfClass:[UIView class]] && ![firstView.lks_involvedRawConstraints containsObject:constraint]) {
if (!firstView.lks_involvedRawConstraints) {
firstView.lks_involvedRawConstraints = [NSMutableArray array];
}
[firstView.lks_involvedRawConstraints addObject:constraint];
}
UIView *secondView = constraint.secondItem;
if ([secondView isKindOfClass:[UIView class]] && ![secondView.lks_involvedRawConstraints containsObject:constraint]) {
if (!secondView.lks_involvedRawConstraints) {
secondView.lks_involvedRawConstraints = [NSMutableArray array];
}
[secondView.lks_involvedRawConstraints addObject:constraint];
}
}];
[rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_addInvolvedRawConstraintsForViewsRootedByView:subview];
}];
}
+ (void)lks_removeInvolvedRawConstraintsForViewsRootedByView:(UIView *)rootView {
[rootView.lks_involvedRawConstraints removeAllObjects];
[rootView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
[self lks_removeInvolvedRawConstraintsForViewsRootedByView:subview];
}];
}
- (void)setLks_involvedRawConstraints:(NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
[self lookin_bindObject:lks_involvedRawConstraints forKey:@"lks_involvedRawConstraints"];
}
- (NSMutableArray<NSLayoutConstraint *> *)lks_involvedRawConstraints {
return [self lookin_getBindObjectForKey:@"lks_involvedRawConstraints"];
}
- (NSArray<LookinAutoLayoutConstraint *> *)lks_constraints {
/**
- lks_involvedRawConstraints self constraints inactive inactive constraints
- constraintsAffectingLayoutForAxis self constraints effectiveConstraints
- constraint effectiveConstraints lks_involvedRawConstraints
· UIWindow minX, minY, width, height effectiveConstraints lks_involvedRawConstraints constraints Xcode Inspector Reveal constraints
· View1 center superview center superview width height effectiveConstraints lks_involvedRawConstraints superview width height View1
*/
NSMutableArray<NSLayoutConstraint *> *effectiveConstraints = [NSMutableArray array];
[effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisHorizontal]];
[effectiveConstraints addObjectsFromArray:[self constraintsAffectingLayoutForAxis:UILayoutConstraintAxisVertical]];
NSArray<LookinAutoLayoutConstraint *> *lookinConstraints = [self.lks_involvedRawConstraints lookin_map:^id(NSUInteger idx, __kindof NSLayoutConstraint *constraint) {
BOOL isEffective = [effectiveConstraints containsObject:constraint];
if ([constraint isActive]) {
// trying to get firstItem or secondItem of an inactive constraint may cause dangling-pointer crash
// https://github.com/QMUI/LookinServer/issues/86
LookinConstraintItemType firstItemType = [self _lks_constraintItemTypeForItem:constraint.firstItem];
LookinConstraintItemType secondItemType = [self _lks_constraintItemTypeForItem:constraint.secondItem];
LookinAutoLayoutConstraint *lookinConstraint = [LookinAutoLayoutConstraint instanceFromNSConstraint:constraint isEffective:isEffective firstItemType:firstItemType secondItemType:secondItemType];
return lookinConstraint;
}
return nil;
}];
return lookinConstraints.count ? lookinConstraints : nil;
}
- (LookinConstraintItemType)_lks_constraintItemTypeForItem:(id)item {
if (!item) {
return LookinConstraintItemTypeNil;
}
if (item == self) {
return LookinConstraintItemTypeSelf;
}
if (item == self.superview) {
return LookinConstraintItemTypeSuper;
}
// runtime UILayoutGuide _UILayoutGuide UIView UIView
if (@available(iOS 9.0, *)) {
if ([item isKindOfClass:[UILayoutGuide class]]) {
return LookinConstraintItemTypeLayoutGuide;
}
}
NSString *className = NSStringFromClass([item class]);
if ([className hasSuffix:@"_UILayoutGuide"]) {
return LookinConstraintItemTypeLayoutGuide;
}
if ([item isKindOfClass:[UIView class]]) {
return LookinConstraintItemTypeView;
}
NSAssert(NO, @"");
return LookinConstraintItemTypeUnknown;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIViewController+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/4/22.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIViewController (LookinServer)
+ (UIViewController *)lks_visibleViewController;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,48 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIViewController+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/4/22.
// https://lookin.work
//
#import "UIViewController+LookinServer.h"
#import "UIView+LookinServer.h"
#import <objc/runtime.h>
#import "LKS_MultiplatformAdapter.h"
@implementation UIViewController (LookinServer)
+ (nullable UIViewController *)lks_visibleViewController {
UIViewController *rootViewController = [LKS_MultiplatformAdapter keyWindow].rootViewController;
UIViewController *visibleViewController = [rootViewController lks_visibleViewControllerIfExist];
return visibleViewController;
}
- (UIViewController *)lks_visibleViewControllerIfExist {
if (self.presentedViewController) {
return [self.presentedViewController lks_visibleViewControllerIfExist];
}
if ([self isKindOfClass:[UINavigationController class]]) {
return [((UINavigationController *)self).visibleViewController lks_visibleViewControllerIfExist];
}
if ([self isKindOfClass:[UITabBarController class]]) {
return [((UITabBarController *)self).selectedViewController lks_visibleViewControllerIfExist];
}
if (self.isViewLoaded && !self.view.hidden && self.view.alpha > 0.01) {
return self;
} else {
return nil;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIVisualEffectView+LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import <UIKit/UIKit.h>
@interface UIVisualEffectView (LookinServer)
- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber;
- (NSNumber *)lks_blurEffectStyleNumber;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,33 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// UIVisualEffectView+LookinServer.m
// LookinServer
//
// Created by Li Kai on 2019/10/8.
// https://lookin.work
//
#import "UIVisualEffectView+LookinServer.h"
#import "UIBlurEffect+LookinServer.h"
@implementation UIVisualEffectView (LookinServer)
- (void)setLks_blurEffectStyleNumber:(NSNumber *)lks_blurEffectStyleNumber {
UIBlurEffectStyle style = [lks_blurEffectStyleNumber integerValue];
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:style];
self.effect = effect;
}
- (NSNumber *)lks_blurEffectStyleNumber {
UIVisualEffect *effect = self.effect;
if (![effect isKindOfClass:[UIBlurEffect class]]) {
return nil;
}
UIBlurEffect *blurEffect = (UIBlurEffect *)effect;
return blurEffect.lks_effectStyleNumber;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Lookin.h
// Lookin
//
// Created by Li Kai on 2018/8/5.
// https://lookin.work
//
#import <UIKit/UIKit.h>
extern NSString *const LKS_ConnectionDidEndNotificationName;
@class LookinConnectionResponseAttachment;
@interface LKS_ConnectionManager : NSObject
+ (instancetype)sharedInstance;
@property(nonatomic, assign) BOOL applicationIsActive;
- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag;
- (void)pushData:(NSObject *)data type:(uint32_t)type;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,268 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer.m
// LookinServer
//
// Created by Li Kai on 2018/8/5.
// https://lookin.work
//
#import "LKS_ConnectionManager.h"
#import "Lookin_PTChannel.h"
#import "LKS_RequestHandler.h"
#import "LookinConnectionResponseAttachment.h"
#import "LKS_ExportManager.h"
#import "LookinServerDefines.h"
#import "LKS_TraceManager.h"
#import "LKS_MultiplatformAdapter.h"
NSString *const LKS_ConnectionDidEndNotificationName = @"LKS_ConnectionDidEndNotificationName";
@interface LKS_ConnectionManager () <Lookin_PTChannelDelegate>
@property(nonatomic, weak) Lookin_PTChannel *peerChannel_;
@property(nonatomic, strong) LKS_RequestHandler *requestHandler;
@end
@implementation LKS_ConnectionManager
+ (instancetype)sharedInstance {
static LKS_ConnectionManager *sharedInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[LKS_ConnectionManager alloc] init];
});
return sharedInstance;
}
+ (void)load {
// init
[LKS_ConnectionManager sharedInstance];
}
- (instancetype)init {
if (self = [super init]) {
NSLog(@"LookinServer - Will launch. Framework version: %@", LOOKIN_SERVER_READABLE_VERSION);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleWillResignActiveNotification) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_2D" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleLocalInspect:) name:@"Lookin_3D" object:nil];
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_Export" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
[[LKS_ExportManager sharedInstance] exportAndShare];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"Lookin_RelationSearch" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
[[LKS_TraceManager sharedInstance] addSearchTarger:note.object];
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleGetLookinInfo:) name:@"GetLookinInfo" object:nil];
self.requestHandler = [LKS_RequestHandler new];
}
return self;
}
- (void)_handleWillResignActiveNotification {
self.applicationIsActive = NO;
if (self.peerChannel_ && ![self.peerChannel_ isConnected]) {
[self.peerChannel_ close];
self.peerChannel_ = nil;
}
}
- (void)_handleApplicationDidBecomeActive {
self.applicationIsActive = YES;
[self searchPortToListenIfNoConnection];
}
- (void)searchPortToListenIfNoConnection {
if ([self.peerChannel_ isConnected]) {
NSLog(@"LookinServer - Abort to search ports. Already has connected channel.");
return;
}
NSLog(@"LookinServer - Searching port to listen...");
[self.peerChannel_ close];
self.peerChannel_ = nil;
if ([self isiOSAppOnMac]) {
[self _tryToListenOnPortFrom:LookinSimulatorIPv4PortNumberStart to:LookinSimulatorIPv4PortNumberEnd current:LookinSimulatorIPv4PortNumberStart];
} else {
[self _tryToListenOnPortFrom:LookinUSBDeviceIPv4PortNumberStart to:LookinUSBDeviceIPv4PortNumberEnd current:LookinUSBDeviceIPv4PortNumberStart];
}
}
- (BOOL)isiOSAppOnMac {
#if TARGET_OS_SIMULATOR
return YES;
#else
if (@available(iOS 14.0, *)) {
// isiOSAppOnMac API iOS 14.0 iOS 14 beta unrecognized selector respondsToSelector
NSProcessInfo *info = [NSProcessInfo processInfo];
if ([info respondsToSelector:@selector(isiOSAppOnMac)]) {
return [info isiOSAppOnMac];
} else if ([info respondsToSelector:@selector(isMacCatalystApp)]) {
return [info isMacCatalystApp];
} else {
return NO;
}
}
if (@available(iOS 13.0, tvOS 13.0, *)) {
return [NSProcessInfo processInfo].isMacCatalystApp;
}
return NO;
#endif
}
- (void)_tryToListenOnPortFrom:(int)fromPort to:(int)toPort current:(int)currentPort {
Lookin_PTChannel *channel = [Lookin_PTChannel channelWithDelegate:self];
channel.targetPort = currentPort;
[channel listenOnPort:currentPort IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) {
if (error) {
if (error.code == 48) {
//
} else {
//
}
if (currentPort < toPort) {
//
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@). Will try anothor address ...", currentPort, error);
[self _tryToListenOnPortFrom:fromPort to:toPort current:(currentPort + 1)];
} else {
//
NSLog(@"LookinServer - 127.0.0.1:%d is unavailable(%@).", currentPort, error);
NSLog(@"LookinServer - Connect failed in the end.");
}
} else {
//
NSLog(@"LookinServer - Connected successfully on 127.0.0.1:%d", currentPort);
// peerChannel_ listening
self.peerChannel_ = channel;
}
}];
}
- (void)dealloc {
if (self.peerChannel_) {
[self.peerChannel_ close];
}
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)respond:(LookinConnectionResponseAttachment *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
[self _sendData:data frameOfType:requestType tag:tag];
}
- (void)pushData:(NSObject *)data type:(uint32_t)type {
[self _sendData:data frameOfType:type tag:0];
}
- (void)_sendData:(NSObject *)data frameOfType:(uint32_t)frameOfType tag:(uint32_t)tag {
if (self.peerChannel_) {
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:data];
dispatch_data_t payload = [archivedData createReferencingDispatchData];
[self.peerChannel_ sendFrameOfType:frameOfType tag:tag withPayload:payload callback:^(NSError *error) {
if (error) {
}
}];
}
}
#pragma mark - Lookin_PTChannelDelegate
- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize {
if (channel != self.peerChannel_) {
return NO;
} else if ([self.requestHandler canHandleRequestType:type]) {
return YES;
} else {
[channel close];
return NO;
}
}
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload {
id object = nil;
if (payload) {
id unarchivedObject = [NSKeyedUnarchiver unarchiveObjectWithData:[NSData dataWithContentsOfDispatchData:payload.dispatchData]];
if ([unarchivedObject isKindOfClass:[LookinConnectionAttachment class]]) {
LookinConnectionAttachment *attachment = (LookinConnectionAttachment *)unarchivedObject;
object = attachment.data;
} else {
object = unarchivedObject;
}
}
[self.requestHandler handleRequestType:type tag:tag object:object];
}
/// Client channel connected
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address {
NSLog(@"LookinServer - channel:%@, acceptConnection:%@", channel.debugTag, otherChannel.debugTag);
Lookin_PTChannel *previousChannel = self.peerChannel_;
otherChannel.targetPort = address.port;
self.peerChannel_ = otherChannel;
[previousChannel cancel];
}
/// Lookin Lookin
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error {
if (self.peerChannel_ != channel) {
// Client listen port Peertalk cancel didAcceptConnection connected channel cancel channel
NSLog(@"LookinServer - Ignore channel%@ end.", channel.debugTag);
return;
}
// Client
NSLog(@"LookinServer - channel%@ DidEndWithError:%@", channel.debugTag, error);
[[NSNotificationCenter defaultCenter] postNotificationName:LKS_ConnectionDidEndNotificationName object:self];
[self searchPortToListenIfNoConnection];
}
#pragma mark - Handler
- (void)_handleLocalInspect:(NSNotification *)note {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Lookin" message:@"Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality." preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
UIWindow *keyWindow = [LKS_MultiplatformAdapter keyWindow];
UIViewController *rootViewController = [keyWindow rootViewController];
[rootViewController presentViewController:alertController animated:YES completion:nil];
NSLog(@"LookinServer - Failed to run local inspection. The feature has been removed. Please use the computer version of Lookin or consider SDKs like FLEX for similar functionality.");
}
- (void)handleGetLookinInfo:(NSNotification *)note {
NSDictionary* userInfo = note.userInfo;
if (!userInfo) {
return;
}
NSMutableDictionary* infoWrapper = userInfo[@"infos"];
if (![infoWrapper isKindOfClass:[NSMutableDictionary class]]) {
NSLog(@"LookinServer - GetLookinInfo failed. Params invalid.");
return;
}
infoWrapper[@"lookinServerVersion"] = LOOKIN_SERVER_READABLE_VERSION;
}
@end
/// 使 NSClassFromString(@"Lookin") LookinServer
@interface Lookin : NSObject
@end
@implementation Lookin
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_RequestHandler.h
// LookinServer
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_RequestHandler : NSObject
- (BOOL)canHandleRequestType:(uint32_t)requestType;
- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,558 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_RequestHandler.m
// LookinServer
//
// Created by Li Kai on 2019/1/15.
// https://lookin.work
//
#import "LKS_RequestHandler.h"
#import "NSObject+LookinServer.h"
#import "UIImage+LookinServer.h"
#import "LKS_ConnectionManager.h"
#import "LookinConnectionResponseAttachment.h"
#import "LookinAttributeModification.h"
#import "LookinDisplayItemDetail.h"
#import "LookinHierarchyInfo.h"
#import "LookinServerDefines.h"
#import <objc/runtime.h>
#import "LookinObject.h"
#import "LookinAppInfo.h"
#import "LKS_AttrGroupsMaker.h"
#import "LKS_InbuiltAttrModificationHandler.h"
#import "LKS_CustomAttrModificationHandler.h"
#import "LKS_AttrModificationPatchHandler.h"
#import "LKS_HierarchyDetailsHandler.h"
#import "LookinStaticAsyncUpdateTask.h"
@interface LKS_RequestHandler ()
@property(nonatomic, strong) NSMutableSet<LKS_HierarchyDetailsHandler *> *activeDetailHandlers;
@end
@implementation LKS_RequestHandler {
NSSet *_validRequestTypes;
}
- (instancetype)init {
if (self = [super init]) {
_validRequestTypes = [NSSet setWithObjects:@(LookinRequestTypePing),
@(LookinRequestTypeApp),
@(LookinRequestTypeHierarchy),
@(LookinRequestTypeInbuiltAttrModification),
@(LookinRequestTypeCustomAttrModification),
@(LookinRequestTypeAttrModificationPatch),
@(LookinRequestTypeHierarchyDetails),
@(LookinRequestTypeFetchObject),
@(LookinRequestTypeAllAttrGroups),
@(LookinRequestTypeAllSelectorNames),
@(LookinRequestTypeInvokeMethod),
@(LookinRequestTypeFetchImageViewImage),
@(LookinRequestTypeModifyRecognizerEnable),
@(LookinPush_CanceHierarchyDetails),
nil];
self.activeDetailHandlers = [NSMutableSet set];
}
return self;
}
- (BOOL)canHandleRequestType:(uint32_t)requestType {
if ([_validRequestTypes containsObject:@(requestType)]) {
return YES;
}
return NO;
}
- (void)handleRequestType:(uint32_t)requestType tag:(uint32_t)tag object:(id)object {
if (requestType == LookinRequestTypePing) {
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
// app 使 appIsInBackground app Lookin
if (![LKS_ConnectionManager sharedInstance].applicationIsActive) {
responseAttachment.appIsInBackground = YES;
}
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeApp) {
//
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary<NSString *, id> *params = object;
BOOL needImages = ((NSNumber *)params[@"needImages"]).boolValue;
NSArray<NSNumber *> *localIdentifiers = params[@"local"];
LookinAppInfo *appInfo = [LookinAppInfo currentInfoWithScreenshot:needImages icon:needImages localIdentifiers:localIdentifiers];
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
responseAttachment.data = appInfo;
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeHierarchy) {
// LookinClient 1.0.4 nil
NSString *clientVersion = nil;
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary<NSString *, id> *params = object;
NSString *version = params[@"clientVersion"];
if ([version isKindOfClass:[NSString class]]) {
clientVersion = version;
}
}
LookinConnectionResponseAttachment *responseAttachment = [LookinConnectionResponseAttachment new];
responseAttachment.data = [LookinHierarchyInfo staticInfoWithLookinVersion:clientVersion];
[[LKS_ConnectionManager sharedInstance] respond:responseAttachment requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeInbuiltAttrModification) {
//
[LKS_InbuiltAttrModificationHandler handleModification:object completion:^(LookinDisplayItemDetail *data, NSError *error) {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
if (error) {
attachment.error = error;
} else {
attachment.data = data;
}
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}];
} else if (requestType == LookinRequestTypeCustomAttrModification) {
BOOL succ = [LKS_CustomAttrModificationHandler handleModification:object];
if (succ) {
[self _submitResponseWithData:nil requestType:requestType tag:tag];
} else {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
}
} else if (requestType == LookinRequestTypeAttrModificationPatch) {
NSArray<LookinStaticAsyncUpdateTask *> *tasks = object;
NSUInteger dataTotalCount = tasks.count;
[LKS_InbuiltAttrModificationHandler handlePatchWithTasks:tasks block:^(LookinDisplayItemDetail *data) {
LookinConnectionResponseAttachment *attrAttachment = [LookinConnectionResponseAttachment new];
attrAttachment.data = data;
attrAttachment.dataTotalCount = dataTotalCount;
attrAttachment.currentDataCount = 1;
[[LKS_ConnectionManager sharedInstance] respond:attrAttachment requestType:LookinRequestTypeAttrModificationPatch tag:tag];
}];
} else if (requestType == LookinRequestTypeHierarchyDetails) {
NSArray<LookinStaticAsyncUpdateTasksPackage *> *packages = object;
NSUInteger responsesDataTotalCount = [packages lookin_reduceInteger:^NSInteger(NSInteger accumulator, NSUInteger idx, LookinStaticAsyncUpdateTasksPackage *package) {
accumulator += package.tasks.count;
return accumulator;
} initialAccumlator:0];
LKS_HierarchyDetailsHandler *handler = [LKS_HierarchyDetailsHandler new];
[self.activeDetailHandlers addObject:handler];
[handler startWithPackages:packages block:^(NSArray<LookinDisplayItemDetail *> *details) {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.data = details;
attachment.dataTotalCount = responsesDataTotalCount;
attachment.currentDataCount = details.count;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:LookinRequestTypeHierarchyDetails tag:tag];
} finishedBlock:^{
[self.activeDetailHandlers removeObject:handler];
}];
} else if (requestType == LookinRequestTypeFetchObject) {
unsigned long oid = ((NSNumber *)object).unsignedLongValue;
NSObject *object = [NSObject lks_objectWithOid:oid];
LookinObject *lookinObj = [LookinObject instanceWithObject:object];
LookinConnectionResponseAttachment *attach = [LookinConnectionResponseAttachment new];
attach.data = lookinObj;
[[LKS_ConnectionManager sharedInstance] respond:attach requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeAllAttrGroups) {
unsigned long oid = ((NSNumber *)object).unsignedLongValue;
CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
if (![layer isKindOfClass:[CALayer class]]) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:LookinRequestTypeAllAttrGroups tag:tag];
return;
}
NSArray<LookinAttributesGroup *> *list = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
[self _submitResponseWithData:list requestType:LookinRequestTypeAllAttrGroups tag:tag];
} else if (requestType == LookinRequestTypeAllSelectorNames) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary *params = object;
Class targetClass = NSClassFromString(params[@"className"]);
BOOL hasArg = [(NSNumber *)params[@"hasArg"] boolValue];
if (!targetClass) {
NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"Didn't find the class named \"%@\". Please input another class and try again."), object];
[self _submitResponseWithError:LookinErrorMake(errorMsg, @"") requestType:requestType tag:tag];
return;
}
NSArray<NSString *> *selNames = [self _methodNameListForClass:targetClass hasArg:hasArg];
[self _submitResponseWithData:selNames requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeInvokeMethod) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary *param = object;
unsigned long oid = [param[@"oid"] unsignedLongValue];
NSString *text = param[@"text"];
if (!text.length) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSObject *targerObj = [NSObject lks_objectWithOid:oid];
if (!targerObj) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
SEL targetSelector = NSSelectorFromString(text);
if (targetSelector && [targerObj respondsToSelector:targetSelector]) {
NSString *resultDescription;
NSObject *resultObject;
NSError *error;
[self _handleInvokeWithObject:targerObj selector:targetSelector resultDescription:&resultDescription resultObject:&resultObject error:&error];
if (error) {
[self _submitResponseWithError:error requestType:requestType tag:tag];
return;
}
NSMutableDictionary *responseData = [NSMutableDictionary dictionaryWithCapacity:2];
if (resultDescription) {
responseData[@"description"] = resultDescription;
}
if (resultObject) {
responseData[@"object"] = resultObject;
}
[self _submitResponseWithData:responseData requestType:requestType tag:tag];
} else {
NSString *errMsg = [NSString stringWithFormat:LKS_Localized(@"%@ doesn't have an instance method called \"%@\"."), NSStringFromClass(targerObj.class), text];
[self _submitResponseWithError:LookinErrorMake(errMsg, @"") requestType:requestType tag:tag];
}
} else if (requestType == LookinPush_CanceHierarchyDetails) {
[self.activeDetailHandlers enumerateObjectsUsingBlock:^(LKS_HierarchyDetailsHandler * _Nonnull handler, BOOL * _Nonnull stop) {
[handler cancel];
}];
[self.activeDetailHandlers removeAllObjects];
} else if (requestType == LookinRequestTypeFetchImageViewImage) {
if (![object isKindOfClass:[NSNumber class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
unsigned long imageViewOid = [(NSNumber *)object unsignedLongValue];
UIImageView *imageView = (UIImageView *)[NSObject lks_objectWithOid:imageViewOid];
if (!imageView) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
if (![imageView isKindOfClass:[UIImageView class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
UIImage *image = imageView.image;
NSData *imageData = [image lookin_data];
[self _submitResponseWithData:imageData requestType:requestType tag:tag];
} else if (requestType == LookinRequestTypeModifyRecognizerEnable) {
if (![object isKindOfClass:[NSDictionary class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
NSDictionary<NSString *, NSNumber *> *params = object;
unsigned long recognizerOid = ((NSNumber *)params[@"oid"]).unsignedLongValue;
BOOL shouldBeEnabled = ((NSNumber *)params[@"enable"]).boolValue;
UIGestureRecognizer *recognizer = (UIGestureRecognizer *)[NSObject lks_objectWithOid:recognizerOid];
if (!recognizer) {
[self _submitResponseWithError:LookinErr_ObjNotFound requestType:requestType tag:tag];
return;
}
if (![recognizer isKindOfClass:[UIGestureRecognizer class]]) {
[self _submitResponseWithError:LookinErr_Inner requestType:requestType tag:tag];
return;
}
recognizer.enabled = shouldBeEnabled;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// dispatch enabled
[self _submitResponseWithData:@(recognizer.enabled) requestType:requestType tag:tag];
});
}
}
- (NSArray<NSString *> *)_methodNameListForClass:(Class)aClass hasArg:(BOOL)hasArg {
NSSet<NSString *> *prefixesToVoid = [NSSet setWithObjects:@"_", @"CA_", @"cpl", @"mf_", @"vs_", @"pep_", @"isNS", @"avkit_", @"PG_", @"px_", @"pl_", @"nsli_", @"pu_", @"pxg_", nil];
NSMutableArray<NSString *> *array = [NSMutableArray array];
Class currentClass = aClass;
while (currentClass) {
NSString *className = NSStringFromClass(currentClass);
BOOL isSystemClass = ([className hasPrefix:@"UI"] || [className hasPrefix:@"CA"] || [className hasPrefix:@"NS"]);
unsigned int methodCount = 0;
Method *methods = class_copyMethodList(currentClass, &methodCount);
for (unsigned int i = 0; i < methodCount; i++) {
NSString *selName = NSStringFromSelector(method_getName(methods[i]));
if (!hasArg && [selName containsString:@":"]) {
continue;
}
if (isSystemClass) {
BOOL invalid = [prefixesToVoid lookin_any:^BOOL(NSString *prefix) {
return [selName hasPrefix:prefix];
}];
if (invalid) {
continue;
}
}
if (selName.length && ![array containsObject:selName]) {
[array addObject:selName];
}
}
if (methods) free(methods);
currentClass = [currentClass superclass];
}
return [array lookin_sortedArrayByStringLength];
}
- (void)_handleInvokeWithObject:(NSObject *)obj selector:(SEL)selector resultDescription:(NSString **)description resultObject:(LookinObject **)resultObject error:(NSError **)error {
NSMethodSignature *signature = [obj methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
*error = LookinErrorMake(LKS_Localized(@"Lookin doesn't support invoking methods with arguments yet."), @"");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:obj];
[invocation setSelector:selector];
[invocation invoke];
const char *returnType = [signature methodReturnType];
if (strcmp(returnType, @encode(void)) == 0) {
//void, do nothing
*description = LookinStringFlag_VoidReturn;
} else if (strcmp(returnType, @encode(char)) == 0) {
char charValue;
[invocation getReturnValue:&charValue];
*description = [NSString stringWithFormat:@"%@", @(charValue)];
} else if (strcmp(returnType, @encode(int)) == 0) {
int intValue;
[invocation getReturnValue:&intValue];
if (intValue == INT_MAX) {
*description = @"INT_MAX";
} else if (intValue == INT_MIN) {
*description = @"INT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(intValue)];
}
} else if (strcmp(returnType, @encode(short)) == 0) {
short shortValue;
[invocation getReturnValue:&shortValue];
if (shortValue == SHRT_MAX) {
*description = @"SHRT_MAX";
} else if (shortValue == SHRT_MIN) {
*description = @"SHRT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(shortValue)];
}
} else if (strcmp(returnType, @encode(long)) == 0) {
long longValue;
[invocation getReturnValue:&longValue];
if (longValue == NSNotFound) {
*description = @"NSNotFound";
} else if (longValue == LONG_MAX) {
*description = @"LONG_MAX";
} else if (longValue == LONG_MIN) {
*description = @"LONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(longValue)];
}
} else if (strcmp(returnType, @encode(long long)) == 0) {
long long longLongValue;
[invocation getReturnValue:&longLongValue];
if (longLongValue == LLONG_MAX) {
*description = @"LLONG_MAX";
} else if (longLongValue == LLONG_MIN) {
*description = @"LLONG_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(longLongValue)];
}
} else if (strcmp(returnType, @encode(unsigned char)) == 0) {
unsigned char ucharValue;
[invocation getReturnValue:&ucharValue];
if (ucharValue == UCHAR_MAX) {
*description = @"UCHAR_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ucharValue)];
}
} else if (strcmp(returnType, @encode(unsigned int)) == 0) {
unsigned int uintValue;
[invocation getReturnValue:&uintValue];
if (uintValue == UINT_MAX) {
*description = @"UINT_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(uintValue)];
}
} else if (strcmp(returnType, @encode(unsigned short)) == 0) {
unsigned short ushortValue;
[invocation getReturnValue:&ushortValue];
if (ushortValue == USHRT_MAX) {
*description = @"USHRT_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ushortValue)];
}
} else if (strcmp(returnType, @encode(unsigned long)) == 0) {
unsigned long ulongValue;
[invocation getReturnValue:&ulongValue];
if (ulongValue == ULONG_MAX) {
*description = @"ULONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ulongValue)];
}
} else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
unsigned long long ulongLongValue;
[invocation getReturnValue:&ulongLongValue];
if (ulongLongValue == ULONG_LONG_MAX) {
*description = @"ULONG_LONG_MAX";
} else {
*description = [NSString stringWithFormat:@"%@", @(ulongLongValue)];
}
} else if (strcmp(returnType, @encode(float)) == 0) {
float floatValue;
[invocation getReturnValue:&floatValue];
if (floatValue == FLT_MAX) {
*description = @"FLT_MAX";
} else if (floatValue == FLT_MIN) {
*description = @"FLT_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(floatValue)];
}
} else if (strcmp(returnType, @encode(double)) == 0) {
double doubleValue;
[invocation getReturnValue:&doubleValue];
if (doubleValue == DBL_MAX) {
*description = @"DBL_MAX";
} else if (doubleValue == DBL_MIN) {
*description = @"DBL_MIN";
} else {
*description = [NSString stringWithFormat:@"%@", @(doubleValue)];
}
} else if (strcmp(returnType, @encode(BOOL)) == 0) {
BOOL boolValue;
[invocation getReturnValue:&boolValue];
*description = boolValue ? @"YES" : @"NO";
} else if (strcmp(returnType, @encode(SEL)) == 0) {
SEL selValue;
[invocation getReturnValue:&selValue];
*description = [NSString stringWithFormat:@"SEL(%@)", NSStringFromSelector(selValue)];
} else if (strcmp(returnType, @encode(Class)) == 0) {
Class classValue;
[invocation getReturnValue:&classValue];
*description = [NSString stringWithFormat:@"<%@>", NSStringFromClass(classValue)];
} else if (strcmp(returnType, @encode(CGPoint)) == 0) {
CGPoint targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGPoint(targetValue);
} else if (strcmp(returnType, @encode(CGVector)) == 0) {
CGVector targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGVector(targetValue);
} else if (strcmp(returnType, @encode(CGSize)) == 0) {
CGSize targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromCGSize(targetValue);
} else if (strcmp(returnType, @encode(CGRect)) == 0) {
CGRect rectValue;
[invocation getReturnValue:&rectValue];
*description = NSStringFromCGRect(rectValue);
} else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
CGAffineTransform rectValue;
[invocation getReturnValue:&rectValue];
*description = NSStringFromCGAffineTransform(rectValue);
} else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
UIEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromUIEdgeInsets(targetValue);
} else if (strcmp(returnType, @encode(UIOffset)) == 0) {
UIOffset targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromUIOffset(targetValue);
} else {
if (@available(iOS 11.0, tvOS 11.0, *)) {
if (strcmp(returnType, @encode(NSDirectionalEdgeInsets)) == 0) {
NSDirectionalEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
*description = NSStringFromDirectionalEdgeInsets(targetValue);
return;
}
}
NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
if ([argType_string hasPrefix:@"@"] || [argType_string hasPrefix:@"^{"]) {
__unsafe_unretained id returnObjValue;
[invocation getReturnValue:&returnObjValue];
if (returnObjValue) {
*description = [NSString stringWithFormat:@"%@", returnObjValue];
LookinObject *parsedLookinObj = [LookinObject instanceWithObject:returnObjValue];
*resultObject = parsedLookinObj;
} else {
*description = @"nil";
}
} else {
*description = [NSString stringWithFormat:LKS_Localized(@"%@ was invoked successfully, but Lookin can't parse the return value:%@"), NSStringFromSelector(selector), argType_string];
}
}
}
- (void)_submitResponseWithError:(NSError *)error requestType:(uint32_t)requestType tag:(uint32_t)tag {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.error = error;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}
- (void)_submitResponseWithData:(NSObject *)data requestType:(uint32_t)requestType tag:(uint32_t)tag {
LookinConnectionResponseAttachment *attachment = [LookinConnectionResponseAttachment new];
attachment.data = data;
[[LKS_ConnectionManager sharedInstance] respond:attachment requestType:requestType tag:tag];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrModificationPatchHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinDisplayItemDetail;
@interface LKS_AttrModificationPatchHandler : NSObject
/**
@param oids 数组内 idx 较小的应该为 displayItems 里的 subItemidx 较大的应该为 superItem
@param lowImageQuality 是否采用低图像质量
@param block 该 block 会被多次调用,其中 tasksTotalCount 是总的调用次数(即可被用来作为 TotalResponseCount
*/
+ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,51 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrModificationPatchHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import "LKS_AttrModificationPatchHandler.h"
#import "LookinDisplayItemDetail.h"
#import "LookinServerDefines.h"
@implementation LKS_AttrModificationPatchHandler
+ (void)handleLayerOids:(NSArray<NSNumber *> *)oids lowImageQuality:(BOOL)lowImageQuality block:(void (^)(LookinDisplayItemDetail *detail, NSUInteger tasksTotalCount, NSError *error))block {
if (!block) {
NSAssert(NO, @"");
return;
}
if (![oids isKindOfClass:[NSArray class]]) {
block(nil, 1, LookinErr_Inner);
return;
}
[oids enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
unsigned long oid = [obj unsignedLongValue];
LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
detail.displayItemOid = oid;
CALayer *layer = (CALayer *)[NSObject lks_objectWithOid:oid];
if (![layer isKindOfClass:[CALayer class]]) {
block(nil, idx + 1, LookinErr_ObjNotFound);
*stop = YES;
return;
}
if (idx == 0) {
detail.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowImageQuality];
detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
} else {
detail.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowImageQuality];
}
block(detail, oids.count, nil);
}];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,19 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrModificationHandler.h
// LookinServer
//
// Created by likaimacbookhome on 2023/11/4.
//
#import <Foundation/Foundation.h>
#import "LookinCustomAttrModification.h"
@interface LKS_CustomAttrModificationHandler : NSObject
/// 返回值表示是否修改成功(有成功调用 setter block 就算成功)
+ (BOOL)handleModification:(LookinCustomAttrModification *)modification;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,155 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrModificationHandler.m
// LookinServer
//
// Created by likaimacbookhome on 2023/11/4.
//
#import "LKS_CustomAttrModificationHandler.h"
#import "LKS_CustomAttrSetterManager.h"
#import "UIColor+LookinServer.h"
@implementation LKS_CustomAttrModificationHandler
+ (BOOL)handleModification:(LookinCustomAttrModification *)modification {
if (!modification || modification.customSetterID.length == 0) {
return NO;
}
switch (modification.attrType) {
case LookinAttrTypeNSString: {
NSString *newValue = modification.value;
if (newValue != nil && ![newValue isKindOfClass:[NSString class]]) {
// nil
return NO;
}
LKS_StringSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getStringSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeDouble: {
NSNumber *newValue = modification.value;
if (![newValue isKindOfClass:[NSNumber class]]) {
return NO;
}
LKS_NumberSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getNumberSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeBOOL: {
NSNumber *newValue = modification.value;
if (![newValue isKindOfClass:[NSNumber class]]) {
return NO;
}
LKS_BoolSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getBoolSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.boolValue);
return YES;
}
case LookinAttrTypeUIColor: {
LKS_ColorSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getColorSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
NSArray<NSNumber *> *newValue = modification.value;
if (newValue == nil) {
// nil
setter(nil);
return YES;
}
if (![newValue isKindOfClass:[NSArray class]]) {
return NO;
}
UIColor *color = [UIColor lks_colorFromRGBAComponents:newValue];
if (!color) {
return NO;
}
setter(color);
return YES;
}
case LookinAttrTypeEnumString: {
NSString *newValue = modification.value;
if (![newValue isKindOfClass:[NSString class]]) {
return NO;
}
LKS_EnumSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getEnumSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue);
return YES;
}
case LookinAttrTypeCGRect: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_RectSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getRectSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGRectValue);
return YES;
}
case LookinAttrTypeCGSize: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_SizeSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getSizeSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGSizeValue);
return YES;
}
case LookinAttrTypeCGPoint: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_PointSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getPointSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.CGPointValue);
return YES;
}
case LookinAttrTypeUIEdgeInsets: {
NSValue *newValue = modification.value;
if (![newValue isKindOfClass:[NSValue class]]) {
return NO;
}
LKS_InsetsSetter setter = [[LKS_CustomAttrSetterManager sharedInstance] getInsetsSetterWithID:modification.customSetterID];
if (!setter) {
return NO;
}
setter(newValue.UIEdgeInsetsValue);
return YES;
}
default:
return NO;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,30 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDetailsHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/20.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinDisplayItemDetail, LookinStaticAsyncUpdateTasksPackage;
typedef void (^LKS_HierarchyDetailsHandler_ProgressBlock)(NSArray<LookinDisplayItemDetail *> *details);
typedef void (^LKS_HierarchyDetailsHandler_FinishBlock)(void);
@interface LKS_HierarchyDetailsHandler : NSObject
/// packages 会按照 idx 从小到大的顺序被执行
/// 全部任务完成时finishBlock 会被调用
/// 如果调用了 cancel则 finishBlock 不会被执行
- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock;
/// 取消所有任务
- (void)cancel;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,148 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDetailsHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/20.
// https://lookin.work
//
#import "LKS_HierarchyDetailsHandler.h"
#import "LookinDisplayItemDetail.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinStaticAsyncUpdateTask.h"
#import "LKS_ConnectionManager.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_HierarchyDisplayItemsMaker.h"
@interface LKS_HierarchyDetailsHandler ()
@property(nonatomic, strong) NSMutableArray<LookinStaticAsyncUpdateTasksPackage *> *taskPackages;
/// oid attrGroups
@property(nonatomic, strong) NSMutableSet<NSNumber *> *attrGroupsSyncedOids;
@property(nonatomic, copy) LKS_HierarchyDetailsHandler_ProgressBlock progressBlock;
@property(nonatomic, copy) LKS_HierarchyDetailsHandler_FinishBlock finishBlock;
@end
@implementation LKS_HierarchyDetailsHandler
- (instancetype)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleConnectionDidEnd:) name:LKS_ConnectionDidEndNotificationName object:nil];
self.attrGroupsSyncedOids = [NSMutableSet set];
}
return self;
}
- (void)startWithPackages:(NSArray<LookinStaticAsyncUpdateTasksPackage *> *)packages block:(LKS_HierarchyDetailsHandler_ProgressBlock)progressBlock finishedBlock:(LKS_HierarchyDetailsHandler_FinishBlock)finishBlock {
if (!progressBlock || !finishBlock) {
NSAssert(NO, @"");
return;
}
if (!packages.count) {
finishBlock();
return;
}
self.taskPackages = [packages mutableCopy];
self.progressBlock = progressBlock;
self.finishBlock = finishBlock;
[UIView lks_rebuildGlobalInvolvedRawConstraints];
[self _dequeueAndHandlePackage];
}
- (void)cancel {
[self.taskPackages removeAllObjects];
}
- (void)_dequeueAndHandlePackage {
dispatch_async(dispatch_get_main_queue(), ^{
LookinStaticAsyncUpdateTasksPackage *package = self.taskPackages.firstObject;
if (!package) {
self.finishBlock();
return;
}
// NSLog(@"LookinServer - will handle tasks, count: %@", @(tasks.count));
NSArray<LookinDisplayItemDetail *> *details = [package.tasks lookin_map:^id(NSUInteger idx, LookinStaticAsyncUpdateTask *task) {
LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
itemDetail.displayItemOid = task.oid;
id object = [NSObject lks_objectWithOid:task.oid];
if (!object || ![object isKindOfClass:[CALayer class]]) {
itemDetail.failureCode = -1;
return itemDetail;
}
CALayer *layer = object;
if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
itemDetail.soloScreenshot = image;
} else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
itemDetail.groupScreenshot = image;
}
BOOL shouldMakeAttr = [self queryIfShouldMakeAttrsFromTask:task];
if (shouldMakeAttr) {
itemDetail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
NSString *version = task.clientReadableVersion;
if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
itemDetail.customAttrGroupList = [maker getGroups];
itemDetail.customDisplayTitle = [maker getCustomDisplayTitle];
itemDetail.danceUISource = [maker getDanceUISource];
}
[self.attrGroupsSyncedOids addObject:@(task.oid)];
}
if (task.needBasisVisualInfo) {
itemDetail.frameValue = [NSValue valueWithCGRect:layer.frame];
itemDetail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
itemDetail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
itemDetail.alphaValue = @(layer.opacity);
}
if (task.needSubitems) {
itemDetail.subitems = [LKS_HierarchyDisplayItemsMaker subitemsOfLayer:layer];
}
return itemDetail;
}];
self.progressBlock(details);
[self.taskPackages removeObjectAtIndex:0];
[self _dequeueAndHandlePackage];
});
}
- (BOOL)queryIfShouldMakeAttrsFromTask:(LookinStaticAsyncUpdateTask *)task {
switch (task.attrRequest) {
case LookinDetailUpdateTaskAttrRequest_Automatic: {
BOOL alreadyMadeBefore = [self.attrGroupsSyncedOids containsObject:@(task.oid)];
return !alreadyMadeBefore;
}
case LookinDetailUpdateTaskAttrRequest_Need:
return YES;
case LookinDetailUpdateTaskAttrRequest_NotNeed:
return NO;
}
NSAssert(NO, @"");
return YES;
}
- (void)_handleConnectionDidEnd:(id)obj {
[self cancel];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_InbuiltAttrModificationHandler.h
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinAttributeModification, LookinDisplayItemDetail, LookinStaticAsyncUpdateTask;
@interface LKS_InbuiltAttrModificationHandler : NSObject
+ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion;
+ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,255 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_InbuiltAttrModificationHandler.m
// LookinServer
//
// Created by Li Kai on 2019/6/12.
// https://lookin.work
//
#import "LKS_InbuiltAttrModificationHandler.h"
#import "UIColor+LookinServer.h"
#import "LookinAttributeModification.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinDisplayItemDetail.h"
#import "LookinStaticAsyncUpdateTask.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrGroupsMaker.h"
@implementation LKS_InbuiltAttrModificationHandler
+ (void)handleModification:(LookinAttributeModification *)modification completion:(void (^)(LookinDisplayItemDetail *data, NSError *error))completion {
if (!completion) {
NSAssert(NO, @"");
return;
}
if (!modification || ![modification isKindOfClass:[LookinAttributeModification class]]) {
completion(nil, LookinErr_Inner);
return;
}
NSObject *receiver = [NSObject lks_objectWithOid:modification.targetOid];
if (!receiver) {
completion(nil, LookinErr_ObjNotFound);
return;
}
NSMethodSignature *setterSignature = [receiver methodSignatureForSelector:modification.setterSelector];
NSInvocation *setterInvocation = [NSInvocation invocationWithMethodSignature:setterSignature];
setterInvocation.target = receiver;
setterInvocation.selector = modification.setterSelector;
if (setterSignature.numberOfArguments != 3 || ![receiver respondsToSelector:modification.setterSelector]) {
completion(nil, LookinErr_Inner);
return;
}
switch (modification.attrType) {
case LookinAttrTypeNone:
case LookinAttrTypeVoid: {
completion(nil, LookinErr_Inner);
return;
}
case LookinAttrTypeChar: {
char expectedValue = [(NSNumber *)modification.value charValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeInt:
case LookinAttrTypeEnumInt: {
int expectedValue = [(NSNumber *)modification.value intValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeShort: {
short expectedValue = [(NSNumber *)modification.value shortValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeLong:
case LookinAttrTypeEnumLong: {
long expectedValue = [(NSNumber *)modification.value longValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeLongLong: {
long long expectedValue = [(NSNumber *)modification.value longLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedChar: {
unsigned char expectedValue = [(NSNumber *)modification.value unsignedCharValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedInt: {
unsigned int expectedValue = [(NSNumber *)modification.value unsignedIntValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedShort: {
unsigned short expectedValue = [(NSNumber *)modification.value unsignedShortValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedLong: {
unsigned long expectedValue = [(NSNumber *)modification.value unsignedLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUnsignedLongLong: {
unsigned long long expectedValue = [(NSNumber *)modification.value unsignedLongLongValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeFloat: {
float expectedValue = [(NSNumber *)modification.value floatValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeDouble: {
double expectedValue = [(NSNumber *)modification.value doubleValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeBOOL: {
BOOL expectedValue = [(NSNumber *)modification.value boolValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeSel: {
SEL expectedValue = NSSelectorFromString(modification.value);
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeClass: {
Class expectedValue = NSClassFromString(modification.value);
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGPoint: {
CGPoint expectedValue = [(NSValue *)modification.value CGPointValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGVector: {
CGVector expectedValue = [(NSValue *)modification.value CGVectorValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGSize: {
CGSize expectedValue = [(NSValue *)modification.value CGSizeValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGRect: {
CGRect expectedValue = [(NSValue *)modification.value CGRectValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCGAffineTransform: {
CGAffineTransform expectedValue = [(NSValue *)modification.value CGAffineTransformValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUIEdgeInsets: {
UIEdgeInsets expectedValue = [(NSValue *)modification.value UIEdgeInsetsValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeUIOffset: {
UIOffset expectedValue = [(NSValue *)modification.value UIOffsetValue];
[setterInvocation setArgument:&expectedValue atIndex:2];
break;
}
case LookinAttrTypeCustomObj:
case LookinAttrTypeNSString: {
NSObject *expectedValue = modification.value;
[setterInvocation setArgument:&expectedValue atIndex:2];
[setterInvocation retainArguments];
break;
}
case LookinAttrTypeUIColor: {
NSArray<NSNumber *> *rgba = modification.value;
UIColor *expectedValue = [UIColor lks_colorFromRGBAComponents:rgba];
[setterInvocation setArgument:&expectedValue atIndex:2];
[setterInvocation retainArguments];
break;
}
default: {
completion(nil, LookinErr_Inner);
return;
}
}
NSError *error = nil;
@try {
[setterInvocation invoke];
} @catch (NSException *exception) {
NSString *errorMsg = [NSString stringWithFormat:LKS_Localized(@"<%@: %p>: an exception was raised when invoking %@. (%@)"), NSStringFromClass(receiver.class), receiver, NSStringFromSelector(modification.setterSelector), exception.reason];
error = [NSError errorWithDomain:LookinErrorDomain code:LookinErrCode_Exception userInfo:@{NSLocalizedDescriptionKey:LKS_Localized(@"The modification may failed."), NSLocalizedRecoverySuggestionErrorKey:errorMsg}];
} @finally {
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
CALayer *layer = nil;
if ([receiver isKindOfClass:[CALayer class]]) {
layer = (CALayer *)receiver;
} else if ([receiver isKindOfClass:[UIView class]]) {
layer = ((UIView *)receiver).layer;
} else {
completion(nil, LookinErr_ObjNotFound);
return;
}
// frame relayout dispatch attrGroups
LookinDisplayItemDetail *detail = [LookinDisplayItemDetail new];
detail.displayItemOid = modification.targetOid;
detail.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
NSString *version = modification.clientReadableVersion;
if (version.length > 0 && [version lookin_numbericOSVersion] >= 10004) {
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
detail.customAttrGroupList = [maker getGroups];
}
detail.frameValue = [NSValue valueWithCGRect:layer.frame];
detail.boundsValue = [NSValue valueWithCGRect:layer.bounds];
detail.hiddenValue = [NSNumber numberWithBool:layer.isHidden];
detail.alphaValue = @(layer.opacity);
completion(detail, error);
});
}
+ (void)handlePatchWithTasks:(NSArray<LookinStaticAsyncUpdateTask *> *)tasks block:(void (^)(LookinDisplayItemDetail *data))block {
if (!block) {
NSAssert(NO, @"");
return;
}
[tasks enumerateObjectsUsingBlock:^(LookinStaticAsyncUpdateTask * _Nonnull task, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItemDetail *itemDetail = [LookinDisplayItemDetail new];
itemDetail.displayItemOid = task.oid;
id object = [NSObject lks_objectWithOid:task.oid];
if (!object || ![object isKindOfClass:[CALayer class]]) {
block(itemDetail);
return;
}
CALayer *layer = object;
if (task.taskType == LookinStaticAsyncUpdateTaskTypeSoloScreenshot) {
UIImage *image = [layer lks_soloScreenshotWithLowQuality:NO];
itemDetail.soloScreenshot = image;
} else if (task.taskType == LookinStaticAsyncUpdateTaskTypeGroupScreenshot) {
UIImage *image = [layer lks_groupScreenshotWithLowQuality:NO];
itemDetail.groupScreenshot = image;
}
block(itemDetail);
}];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,17 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer.h
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#ifndef LookinServer_h
#define LookinServer_h
#endif /* LookinServer_h */
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKSConfigManager.h
// LookinServer
//
// Created by likai.123 on 2023/1/10.
//
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LKSConfigManager : NSObject
+ (NSArray<NSString *> *)collapsedClassList;
+ (NSDictionary<NSString *, UIColor *> *)colorAlias;
+ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,195 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKSConfigManager.m
// LookinServer
//
// Created by likai.123 on 2023/1/10.
//
#import "LKSConfigManager.h"
#import "NSArray+Lookin.h"
#import "CALayer+LookinServer.h"
@implementation LKSConfigManager
+ (NSArray<NSString *> *)collapsedClassList {
NSArray<NSString *> *result = [self queryCollapsedClassListWithClass:[NSObject class] selector:@"lookin_collapsedClassList"];
if (result) {
return result;
}
// Legacy logic. Deprecated.
Class configClass = NSClassFromString(@"LookinConfig");
if (!configClass) {
return nil;
}
NSArray<NSString *> *legacyCodeResult = [self queryCollapsedClassListWithClass:configClass selector:@"collapsedClasses"];
return legacyCodeResult;
}
+ (NSArray<NSString *> *)queryCollapsedClassListWithClass:(Class)class selector:(NSString *)selectorName {
SEL selector = NSSelectorFromString(selectorName);
if (![class respondsToSelector:selector]) {
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
[invocation setTarget:class];
[invocation setSelector:selector];
[invocation invoke];
void *arrayValue;
[invocation getReturnValue:&arrayValue];
id classList = (__bridge id)(arrayValue);
if ([classList isKindOfClass:[NSArray class]]) {
NSArray *validClassList = [((NSArray *)classList) lookin_filter:^BOOL(id obj) {
return [obj isKindOfClass:[NSString class]];
}];
return [validClassList copy];
}
return nil;
}
+ (NSDictionary<NSString *, UIColor *> *)colorAlias {
NSDictionary<NSString *, UIColor *> *result = [self queryColorAliasWithClass:[NSObject class] selector:@"lookin_colorAlias"];
if (result) {
return result;
}
// Legacy logic. Deprecated.
Class configClass = NSClassFromString(@"LookinConfig");
if (!configClass) {
return nil;
}
NSDictionary<NSString *, UIColor *> *legacyCodeResult = [self queryColorAliasWithClass:configClass selector:@"colors"];
return legacyCodeResult;
}
+ (NSDictionary<NSString *, UIColor *> *)queryColorAliasWithClass:(Class)class selector:(NSString *)selectorName {
SEL selector = NSSelectorFromString(selectorName);
if (![class respondsToSelector:selector]) {
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[class methodSignatureForSelector:selector]];
[invocation setTarget:class];
[invocation setSelector:selector];
[invocation invoke];
void *dictValue;
[invocation getReturnValue:&dictValue];
id colorAlias = (__bridge id)(dictValue);
if ([colorAlias isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *validDictionary = [NSMutableDictionary dictionary];
[(NSDictionary *)colorAlias enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([key isKindOfClass:[NSString class]]) {
if ([obj isKindOfClass:[UIColor class]]) {
[validDictionary setObject:obj forKey:key];
} else if ([obj isKindOfClass:[NSDictionary class]]) {
__block BOOL isValidSubDict = YES;
[((NSDictionary *)obj) enumerateKeysAndObjectsUsingBlock:^(id _Nonnull subKey, id _Nonnull subObj, BOOL * _Nonnull stop) {
if (![subKey isKindOfClass:[NSString class]] || ![subObj isKindOfClass:[UIColor class]]) {
isValidSubDict = NO;
*stop = YES;
}
}];
if (isValidSubDict) {
[validDictionary setObject:obj forKey:key];
}
}
}
}];
return [validDictionary copy];
}
return nil;
}
+ (BOOL)shouldCaptureScreenshotOfLayer:(CALayer *)layer {
if (!layer) {
return YES;
}
if (![self shouldCaptureImageOfLayer:layer]) {
return NO;
}
UIView *view = layer.lks_hostView;
if (!view) {
return YES;
}
if (![self shouldCaptureImageOfView:view]) {
return NO;
}
return YES;
}
+ (BOOL)shouldCaptureImageOfLayer:(CALayer *)layer {
if (!layer) {
return YES;
}
SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfLayer:");
if ([NSObject respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
[invocation setTarget:[NSObject class]];
[invocation setSelector:selector];
[invocation setArgument:&layer atIndex:2];
[invocation invoke];
BOOL resultValue = YES;
[invocation getReturnValue:&resultValue];
if (!resultValue) {
return NO;
}
}
SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
if ([layer respondsToSelector:selector2]) {
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[layer methodSignatureForSelector:selector2]];
[invocation2 setTarget:layer];
[invocation2 setSelector:selector2];
[invocation2 invoke];
BOOL resultValue2 = YES;
[invocation2 getReturnValue:&resultValue2];
if (!resultValue2) {
return NO;
}
}
return YES;
}
+ (BOOL)shouldCaptureImageOfView:(UIView *)view {
if (!view) {
return YES;
}
SEL selector = NSSelectorFromString(@"lookin_shouldCaptureImageOfView:");
if ([NSObject respondsToSelector:selector]) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[NSObject methodSignatureForSelector:selector]];
[invocation setTarget:[NSObject class]];
[invocation setSelector:selector];
[invocation setArgument:&view atIndex:2];
[invocation invoke];
BOOL resultValue = YES;
[invocation getReturnValue:&resultValue];
if (!resultValue) {
return NO;
}
}
SEL selector2 = NSSelectorFromString(@"lookin_shouldCaptureImage");
if ([view respondsToSelector:selector2]) {
NSInvocation *invocation2 = [NSInvocation invocationWithMethodSignature:[view methodSignatureForSelector:selector2]];
[invocation2 setTarget:view];
[invocation2 setSelector:selector2];
[invocation2 invoke];
BOOL resultValue2 = YES;
[invocation2 getReturnValue:&resultValue2];
if (!resultValue2) {
return NO;
}
}
return YES;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrGroupsMaker.h
// LookinServer
//
// Created by Li Kai on 2019/6/6.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinAttributesGroup;
@interface LKS_AttrGroupsMaker : NSObject
+ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,302 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_AttrGroupsMaker.m
// LookinServer
//
// Created by Li Kai on 2019/6/6.
// https://lookin.work
//
#import "LKS_AttrGroupsMaker.h"
#import "LookinAttributesGroup.h"
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "LookinDashboardBlueprint.h"
#import "LookinIvarTrace.h"
#import "UIColor+LookinServer.h"
#import "LookinServerDefines.h"
@implementation LKS_AttrGroupsMaker
+ (NSArray<LookinAttributesGroup *> *)attrGroupsForLayer:(CALayer *)layer {
if (!layer) {
NSAssert(NO, @"");
return nil;
}
NSArray<LookinAttributesGroup *> *groups = [[LookinDashboardBlueprint groupIDs] lookin_map:^id(NSUInteger idx, LookinAttrGroupIdentifier groupID) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.identifier = groupID;
NSArray<LookinAttrSectionIdentifier> *secIDs = [LookinDashboardBlueprint sectionIDsForGroupID:groupID];
group.attrSections = [secIDs lookin_map:^id(NSUInteger idx, LookinAttrSectionIdentifier secID) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = secID;
NSArray<LookinAttrIdentifier> *attrIDs = [LookinDashboardBlueprint attrIDsForSectionID:secID];
sec.attributes = [attrIDs lookin_map:^id(NSUInteger idx, LookinAttrIdentifier attrID) {
NSInteger minAvailableVersion = [LookinDashboardBlueprint minAvailableOSVersionWithAttrID:attrID];
if (minAvailableVersion > 0 && (NSProcessInfo.processInfo.operatingSystemVersion.majorVersion < minAvailableVersion)) {
// iOS
return nil;
}
id targetObj = nil;
if ([LookinDashboardBlueprint isUIViewPropertyWithAttrID:attrID]) {
targetObj = layer.lks_hostView;
} else {
targetObj = layer;
}
if (targetObj) {
Class targetClass = NSClassFromString([LookinDashboardBlueprint classNameWithAttrID:attrID]);
if (![targetObj isKindOfClass:targetClass]) {
return nil;
}
LookinAttribute *attr = [self _attributeWithIdentifer:attrID targetObject:targetObj];
return attr;
} else {
return nil;
}
}];
if (sec.attributes.count) {
return sec;
} else {
return nil;
}
}];
if ([groupID isEqualToString:LookinAttrGroup_AutoLayout]) {
// AutoLayout Constraints Hugging Resistance AutoLayout
BOOL hasConstraits = [group.attrSections lookin_any:^BOOL(LookinAttributesSection *obj) {
return [obj.identifier isEqualToString:LookinAttrSec_AutoLayout_Constraints];
}];
if (!hasConstraits) {
return nil;
}
}
if (group.attrSections.count) {
return group;
} else {
return nil;
}
}];
return groups;
}
+ (LookinAttribute *)_attributeWithIdentifer:(LookinAttrIdentifier)identifier targetObject:(id)target {
if (!target) {
NSAssert(NO, @"");
return nil;
}
LookinAttribute *attribute = [LookinAttribute new];
attribute.identifier = identifier;
SEL getter = [LookinDashboardBlueprint getterWithAttrID:identifier];
if (!getter) {
NSAssert(NO, @"");
return nil;
}
if (![target respondsToSelector:getter]) {
// QMUI QMUI
return nil;
}
NSMethodSignature *signature = [target methodSignatureForSelector:getter];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"getter 不可以有参数");
return nil;
}
if (strcmp([signature methodReturnType], @encode(void)) == 0) {
NSAssert(NO, @"getter 返回值不能为 void");
return nil;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = target;
invocation.selector = getter;
[invocation invoke];
const char *returnType = [signature methodReturnType];
if (strcmp(returnType, @encode(char)) == 0) {
char targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeChar;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(int)) == 0) {
int targetValue;
[invocation getReturnValue:&targetValue];
attribute.value = @(targetValue);
if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
attribute.attrType = LookinAttrTypeEnumInt;
} else {
attribute.attrType = LookinAttrTypeInt;
}
} else if (strcmp(returnType, @encode(short)) == 0) {
short targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeShort;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(long)) == 0) {
long targetValue;
[invocation getReturnValue:&targetValue];
attribute.value = @(targetValue);
if ([LookinDashboardBlueprint enumListNameWithAttrID:identifier]) {
attribute.attrType = LookinAttrTypeEnumLong;
} else {
attribute.attrType = LookinAttrTypeLong;
}
} else if (strcmp(returnType, @encode(long long)) == 0) {
long long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeLongLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned char)) == 0) {
unsigned char targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedChar;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned int)) == 0) {
unsigned int targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedInt;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned short)) == 0) {
unsigned short targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedShort;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned long)) == 0) {
unsigned long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(unsigned long long)) == 0) {
unsigned long long targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUnsignedLongLong;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(float)) == 0) {
float targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeFloat;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(double)) == 0) {
double targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeDouble;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(BOOL)) == 0) {
BOOL targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeBOOL;
attribute.value = @(targetValue);
} else if (strcmp(returnType, @encode(SEL)) == 0) {
SEL targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeSel;
attribute.value = NSStringFromSelector(targetValue);
} else if (strcmp(returnType, @encode(Class)) == 0) {
Class targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeClass;
attribute.value = NSStringFromClass(targetValue);
} else if (strcmp(returnType, @encode(CGPoint)) == 0) {
CGPoint targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGPoint;
attribute.value = [NSValue valueWithCGPoint:targetValue];
} else if (strcmp(returnType, @encode(CGVector)) == 0) {
CGVector targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGVector;
attribute.value = [NSValue valueWithCGVector:targetValue];
} else if (strcmp(returnType, @encode(CGSize)) == 0) {
CGSize targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGSize;
attribute.value = [NSValue valueWithCGSize:targetValue];
} else if (strcmp(returnType, @encode(CGRect)) == 0) {
CGRect targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGRect;
attribute.value = [NSValue valueWithCGRect:targetValue];
} else if (strcmp(returnType, @encode(CGAffineTransform)) == 0) {
CGAffineTransform targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeCGAffineTransform;
attribute.value = [NSValue valueWithCGAffineTransform:targetValue];
} else if (strcmp(returnType, @encode(UIEdgeInsets)) == 0) {
UIEdgeInsets targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUIEdgeInsets;
attribute.value = [NSValue valueWithUIEdgeInsets:targetValue];
} else if (strcmp(returnType, @encode(UIOffset)) == 0) {
UIOffset targetValue;
[invocation getReturnValue:&targetValue];
attribute.attrType = LookinAttrTypeUIOffset;
attribute.value = [NSValue valueWithUIOffset:targetValue];
} else {
NSString *argType_string = [[NSString alloc] lookin_safeInitWithUTF8String:returnType];
if ([argType_string hasPrefix:@"@"]) {
__unsafe_unretained id returnObjValue;
[invocation getReturnValue:&returnObjValue];
if (!returnObjValue && [LookinDashboardBlueprint hideIfNilWithAttrID:identifier]) {
// value nil
return nil;
}
attribute.attrType = [LookinDashboardBlueprint objectAttrTypeWithAttrID:identifier];
if (attribute.attrType == LookinAttrTypeUIColor) {
if (returnObjValue == nil) {
attribute.value = nil;
} else if ([returnObjValue isKindOfClass:[UIColor class]] && [returnObjValue respondsToSelector:@selector(lks_rgbaComponents)]) {
attribute.value = [returnObjValue lks_rgbaComponents];
} else {
// https://github.com/QMUI/LookinServer/issues/124
return nil;
}
} else {
attribute.value = returnObjValue;
}
} else {
NSAssert(NO, @"不支持解析该类型的返回值");
return nil;
}
}
return attribute;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrGroupsMaker.h
// LookinServer
//
// Created by LikaiMacStudioWork on 2023/10/31.
//
#import "LookinDefines.h"
@class LookinAttributesGroup;
@interface LKS_CustomAttrGroupsMaker : NSObject
- (instancetype)initWithLayer:(CALayer *)layer;
- (void)execute;
- (NSArray<LookinAttributesGroup *> *)getGroups;
- (NSString *)getCustomDisplayTitle;
- (NSString *)getDanceUISource;
+ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,486 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrGroupsMaker.m
// LookinServer
//
// Created by LikaiMacStudioWork on 2023/10/31.
//
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_AttrGroupsMaker.h"
#import "LookinAttributesGroup.h"
#import "LookinAttributesSection.h"
#import "LookinAttribute.h"
#import "LookinDashboardBlueprint.h"
#import "LookinIvarTrace.h"
#import "UIColor+LookinServer.h"
#import "LookinServerDefines.h"
#import "LKS_CustomAttrSetterManager.h"
@interface LKS_CustomAttrGroupsMaker ()
/// key section title
@property(nonatomic, strong) NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *sectionAndAttrs;
@property(nonatomic, copy) NSString *resolvedCustomDisplayTitle;
@property(nonatomic, copy) NSString *resolvedDanceUISource;
@property(nonatomic, strong) NSMutableArray *resolvedGroups;
@property(nonatomic, weak) CALayer *layer;
@end
@implementation LKS_CustomAttrGroupsMaker
- (instancetype)initWithLayer:(CALayer *)layer {
if (self = [super init]) {
self.sectionAndAttrs = [NSMutableDictionary dictionary];
self.layer = layer;
}
return self;
}
- (void)execute {
if (!self.layer) {
NSAssert(NO, @"");
return;
}
NSMutableArray<NSString *> *selectors = [NSMutableArray array];
[selectors addObject:@"lookin_customDebugInfos"];
for (int i = 0; i < 5; i++) {
[selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
}
for (NSString *name in selectors) {
[self makeAttrsForViewOrLayer:self.layer selectorName:name];
UIView *view = self.layer.lks_hostView;
if (view) {
[self makeAttrsForViewOrLayer:view selectorName:name];
}
}
if ([self.sectionAndAttrs count] == 0) {
return;
}
NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
[self.sectionAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.userCustomTitle = groupTitle;
group.identifier = LookinAttrGroup_UserCustom;
NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
[attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = LookinAttrSec_UserCustom;
sec.attributes = @[attr];
[sections addObject:sec];
}];
group.attrSections = sections;
[groups addObject:group];
}];
[groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
return [obj1.userCustomTitle compare:obj2.userCustomTitle];
}];
self.resolvedGroups = groups;
}
- (void)makeAttrsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
if (!viewOrLayer || !selectorName.length) {
return;
}
if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
return;
}
SEL selector = NSSelectorFromString(selectorName);
if (![viewOrLayer respondsToSelector:selector]) {
return;
}
NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:viewOrLayer];
[invocation setSelector:selector];
[invocation invoke];
//
NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
[invocation getReturnValue:&tempRawData];
if (!tempRawData || ![tempRawData isKindOfClass:[NSDictionary class]]) {
return;
}
NSDictionary<NSString *, id> *rawData = tempRawData;
NSArray *rawProperties = rawData[@"properties"];
NSString *customTitle = rawData[@"title"];
if (customTitle && [customTitle isKindOfClass:[NSString class]] && customTitle.length > 0) {
self.resolvedCustomDisplayTitle = customTitle;
}
NSString *danceSource = rawData[@"lookin_source"];
if (danceSource && [danceSource isKindOfClass:[NSString class]] && danceSource.length > 0) {
self.resolvedDanceUISource = danceSource;
}
[self makeAttrsFromRawProperties:rawProperties];
}
- (void)makeAttrsFromRawProperties:(NSArray *)rawProperties {
if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
return;
}
for (NSDictionary<NSString *, id> *dict in rawProperties) {
NSString *groupTitle;
LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:YES groupTitle:&groupTitle];
if (!attr) {
continue;
}
if (!self.sectionAndAttrs[groupTitle]) {
self.sectionAndAttrs[groupTitle] = [NSMutableArray array];
}
[self.sectionAndAttrs[groupTitle] addObject:attr];
}
}
+ (LookinAttribute *)attrFromRawDict:(NSDictionary *)dict saveCustomSetter:(BOOL)saveCustomSetter groupTitle:(inout NSString **)inoutGroupTitle {
LookinAttribute *attr = [LookinAttribute new];
attr.identifier = LookinAttr_UserCustom;
NSString *title = dict[@"title"];
NSString *type = dict[@"valueType"];
NSString *section = dict[@"section"];
id value = dict[@"value"];
if (!title || ![title isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong title");
return nil;
}
if (!type || ![type isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong valueType");
return nil;
}
if (!section || ![section isKindOfClass:[NSString class]] || section.length == 0) {
*inoutGroupTitle = @"Custom";
} else {
*inoutGroupTitle = section;
}
attr.displayTitle = title;
NSString *fixedType = type.lowercaseString;
if ([fixedType isEqualToString:@"string"]) {
if (value != nil && ![value isKindOfClass:[NSString class]]) {
// nil
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeNSString;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_StringSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveStringSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"number"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeDouble;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_NumberSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveNumberSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"bool"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeBOOL;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_BoolSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveBoolSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"color"]) {
if (value != nil && ![value isKindOfClass:[UIColor class]]) {
// nil
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeUIColor;
attr.value = [(UIColor *)value lks_rgbaComponents];
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_ColorSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveColorSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"rect"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGRect;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_RectSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveRectSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"size"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGSize;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_SizeSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveSizeSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"point"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeCGPoint;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_PointSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] savePointSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"insets"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeUIEdgeInsets;
attr.value = value;
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_InsetsSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveInsetsSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"shadow"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSDictionary class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
NSDictionary *shadowInfo = value;
if (![shadowInfo[@"offset"] isKindOfClass:[NSValue class]]) {
NSLog(@"LookinServer - Wrong value. No offset.");
return nil;
}
if (![shadowInfo[@"opacity"] isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value. No opacity.");
return nil;
}
if (![shadowInfo[@"radius"] isKindOfClass:[NSNumber class]]) {
NSLog(@"LookinServer - Wrong value. No radius.");
return nil;
}
NSMutableDictionary *checkedShadowInfo = [@{
@"offset": shadowInfo[@"offset"],
@"opacity": shadowInfo[@"opacity"],
@"radius": shadowInfo[@"radius"]
} mutableCopy];
if ([shadowInfo[@"color"] isKindOfClass:[UIColor class]]) {
checkedShadowInfo[@"color"] = [(UIColor *)shadowInfo[@"color"] lks_rgbaComponents];
}
attr.attrType = LookinAttrTypeShadow;
attr.value = checkedShadowInfo;
return attr;
}
if ([fixedType isEqualToString:@"enum"]) {
if (value == nil) {
NSLog(@"LookinServer - No value.");
return nil;
}
if (![value isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeEnumString;
attr.value = value;
NSArray<NSString *> *allEnumCases = dict[@"allEnumCases"];
if ([allEnumCases isKindOfClass:[NSArray class]]) {
attr.extraValue = allEnumCases;
}
if (saveCustomSetter && dict[@"retainedSetter"]) {
NSString *uniqueID = [[NSUUID new] UUIDString];
LKS_EnumSetter setter = dict[@"retainedSetter"];
[[LKS_CustomAttrSetterManager sharedInstance] saveEnumSetter:setter uniqueID:uniqueID];
attr.customSetterID = uniqueID;
}
return attr;
}
if ([fixedType isEqualToString:@"json"]) {
if (![value isKindOfClass:[NSString class]]) {
NSLog(@"LookinServer - Wrong value type.");
return nil;
}
attr.attrType = LookinAttrTypeJson;
attr.value = value;
return attr;
}
NSLog(@"LookinServer - Unsupported value type.");
return nil;
}
- (NSArray<LookinAttributesGroup *> *)getGroups {
return self.resolvedGroups;
}
- (NSString *)getCustomDisplayTitle {
return self.resolvedCustomDisplayTitle;
}
- (NSString *)getDanceUISource {
return self.resolvedDanceUISource;
}
+ (NSArray<LookinAttributesGroup *> *)makeGroupsFromRawProperties:(NSArray *)rawProperties saveCustomSetter:(BOOL)saveCustomSetter {
if (!rawProperties || ![rawProperties isKindOfClass:[NSArray class]]) {
return nil;
}
// key group title
NSMutableDictionary<NSString *, NSMutableArray<LookinAttribute *> *> *groupTitleAndAttrs = [NSMutableDictionary dictionary];
for (NSDictionary<NSString *, id> *dict in rawProperties) {
NSString *groupTitle;
LookinAttribute *attr = [LKS_CustomAttrGroupsMaker attrFromRawDict:dict saveCustomSetter:saveCustomSetter groupTitle:&groupTitle];
if (!attr) {
continue;
}
if (!groupTitleAndAttrs[groupTitle]) {
groupTitleAndAttrs[groupTitle] = [NSMutableArray array];
}
[groupTitleAndAttrs[groupTitle] addObject:attr];
}
if ([groupTitleAndAttrs count] == 0) {
return nil;
}
NSMutableArray<LookinAttributesGroup *> *groups = [NSMutableArray array];
[groupTitleAndAttrs enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull groupTitle, NSMutableArray<LookinAttribute *> * _Nonnull attrs, BOOL * _Nonnull stop) {
LookinAttributesGroup *group = [LookinAttributesGroup new];
group.userCustomTitle = groupTitle;
group.identifier = LookinAttrGroup_UserCustom;
NSMutableArray<LookinAttributesSection *> *sections = [NSMutableArray array];
[attrs enumerateObjectsUsingBlock:^(LookinAttribute * _Nonnull attr, NSUInteger idx, BOOL * _Nonnull stop) {
LookinAttributesSection *sec = [LookinAttributesSection new];
sec.identifier = LookinAttrSec_UserCustom;
sec.attributes = @[attr];
[sections addObject:sec];
}];
group.attrSections = sections;
[groups addObject:group];
}];
[groups sortedArrayUsingComparator:^NSComparisonResult(LookinAttributesGroup *obj1, LookinAttributesGroup *obj2) {
return [obj1.userCustomTitle compare:obj2.userCustomTitle];
}];
return [groups copy];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,56 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrSetterManager.h
// LookinServer
//
// Created by likai.123 on 2023/11/4.
//
#import <UIKit/UIKit.h>
typedef void(^LKS_StringSetter)(NSString *);
typedef void(^LKS_NumberSetter)(NSNumber *);
typedef void(^LKS_BoolSetter)(BOOL);
typedef void(^LKS_ColorSetter)(UIColor *);
typedef void(^LKS_EnumSetter)(NSString *);
typedef void(^LKS_RectSetter)(CGRect);
typedef void(^LKS_SizeSetter)(CGSize);
typedef void(^LKS_PointSetter)(CGPoint);
typedef void(^LKS_InsetsSetter)(UIEdgeInsets);
@interface LKS_CustomAttrSetterManager : NSObject
+ (instancetype)sharedInstance;
- (void)removeAll;
- (void)saveStringSetter:(LKS_StringSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_StringSetter)getStringSetterWithID:(NSString *)uniqueID;
- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID;
- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID;
- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID;
- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID;
- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID;
- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID;
- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID;
- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID;
- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,117 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomAttrSetterManager.m
// LookinServer
//
// Created by likai.123 on 2023/11/4.
//
#import "LKS_CustomAttrSetterManager.h"
@interface LKS_CustomAttrSetterManager ()
@property(nonatomic, strong) NSMutableDictionary *settersMap;
@end
@implementation LKS_CustomAttrSetterManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_CustomAttrSetterManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (instancetype)init {
self = [super init];
if (self) {
self.settersMap = [NSMutableDictionary new];
}
return self;
}
- (void)removeAll {
[self.settersMap removeAllObjects];
}
- (void)saveStringSetter:(nonnull LKS_StringSetter)setter uniqueID:(nonnull NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (nullable LKS_StringSetter)getStringSetterWithID:(nonnull NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveNumberSetter:(LKS_NumberSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (nullable LKS_NumberSetter)getNumberSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveBoolSetter:(LKS_BoolSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_BoolSetter)getBoolSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveColorSetter:(LKS_ColorSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_ColorSetter)getColorSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveEnumSetter:(LKS_EnumSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_EnumSetter)getEnumSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveRectSetter:(LKS_RectSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_RectSetter)getRectSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveSizeSetter:(LKS_SizeSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_SizeSetter)getSizeSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)savePointSetter:(LKS_PointSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_PointSetter)getPointSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
- (void)saveInsetsSetter:(LKS_InsetsSetter)setter uniqueID:(NSString *)uniqueID {
self.settersMap[uniqueID] = setter;
}
- (LKS_InsetsSetter)getInsetsSetterWithID:(NSString *)uniqueID {
return self.settersMap[uniqueID];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,22 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomDisplayItemsMaker.h
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import <UIKit/UIKit.h>
@class LookinDisplayItem;
@interface LKS_CustomDisplayItemsMaker : NSObject
- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter;
- (NSArray<LookinDisplayItem *> *)make;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,144 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_CustomDisplayItemsMaker.m
// LookinServer
//
// Created by likai.123 on 2023/11/1.
//
#import "LKS_CustomDisplayItemsMaker.h"
#import "CALayer+LookinServer.h"
#import "LookinDisplayItem.h"
#import "NSArray+Lookin.h"
#import "LKS_CustomAttrGroupsMaker.h"
@interface LKS_CustomDisplayItemsMaker ()
@property(nonatomic, weak) CALayer *layer;
@property(nonatomic, assign) BOOL saveAttrSetter;
@property(nonatomic, strong) NSMutableArray *allSubitems;
@end
@implementation LKS_CustomDisplayItemsMaker
- (instancetype)initWithLayer:(CALayer *)layer saveAttrSetter:(BOOL)saveAttrSetter {
if (self = [super init]) {
self.layer = layer;
self.saveAttrSetter = saveAttrSetter;
self.allSubitems = [NSMutableArray array];
}
return self;
}
- (NSArray<LookinDisplayItem *> *)make {
if (!self.layer) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray<NSString *> *selectors = [NSMutableArray array];
[selectors addObject:@"lookin_customDebugInfos"];
for (int i = 0; i < 5; i++) {
[selectors addObject:[NSString stringWithFormat:@"lookin_customDebugInfos_%@", @(i)]];
}
for (NSString *name in selectors) {
[self makeSubitemsForViewOrLayer:self.layer selectorName:name];
UIView *view = self.layer.lks_hostView;
if (view) {
[self makeSubitemsForViewOrLayer:view selectorName:name];
}
}
if (self.allSubitems.count) {
return self.allSubitems;
} else {
return nil;
}
}
- (void)makeSubitemsForViewOrLayer:(id)viewOrLayer selectorName:(NSString *)selectorName {
if (!viewOrLayer || !selectorName.length) {
return;
}
if (![viewOrLayer isKindOfClass:[UIView class]] && ![viewOrLayer isKindOfClass:[CALayer class]]) {
return;
}
SEL selector = NSSelectorFromString(selectorName);
if (![viewOrLayer respondsToSelector:selector]) {
return;
}
NSMethodSignature *signature = [viewOrLayer methodSignatureForSelector:selector];
if (signature.numberOfArguments > 2) {
NSAssert(NO, @"LookinServer - There should be no explicit parameters.");
return;
}
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setTarget:viewOrLayer];
[invocation setSelector:selector];
[invocation invoke];
//
NSDictionary<NSString *, id> * __unsafe_unretained tempRawData;
[invocation getReturnValue:&tempRawData];
NSDictionary<NSString *, id> *rawData = tempRawData;
[self makeSubitemsFromRawData:rawData];
}
- (void)makeSubitemsFromRawData:(NSDictionary<NSString *, id> *)data {
if (!data || ![data isKindOfClass:[NSDictionary class]]) {
return;
}
NSArray *rawSubviews = data[@"subviews"];
NSArray<LookinDisplayItem *> *newSubitems = [self displayItemsFromRawArray:rawSubviews];
if (newSubitems) {
[self.allSubitems addObjectsFromArray:newSubitems];
}
}
- (NSArray<LookinDisplayItem *> *)displayItemsFromRawArray:(NSArray<NSDictionary *> *)rawArray {
if (!rawArray || ![rawArray isKindOfClass:[NSArray class]]) {
return nil;
}
NSArray *items = [rawArray lookin_map:^id(NSUInteger idx, NSDictionary *rawDict) {
if (![rawDict isKindOfClass:[NSDictionary class]]) {
return nil;
}
return [self displayItemFromRawDict:rawDict];
}];
return items;
}
- (LookinDisplayItem *)displayItemFromRawDict:(NSDictionary<NSString *, id> *)dict {
NSString *title = dict[@"title"];
NSString *subtitle = dict[@"subtitle"];
NSValue *frameValue = dict[@"frameInWindow"];
NSArray *properties = dict[@"properties"];
NSArray *subviews = dict[@"subviews"];
NSString *danceSource = dict[@"lookin_source"];
if (![title isKindOfClass:[NSString class]]) {
return nil;
}
LookinDisplayItem *newItem = [LookinDisplayItem new];
if (subviews && [subviews isKindOfClass:[NSArray class]]) {
newItem.subitems = [self displayItemsFromRawArray:subviews];
}
newItem.isHidden = NO;
newItem.alpha = 1.0;
newItem.customInfo = [LookinCustomDisplayItemInfo new];
newItem.customInfo.title = title;
newItem.customInfo.subtitle = subtitle;
newItem.customInfo.frameInWindow = frameValue;
newItem.customInfo.danceuiSource = danceSource;
newItem.customAttrGroupList = [LKS_CustomAttrGroupsMaker makeGroupsFromRawProperties:properties saveCustomSetter:self.saveAttrSetter];
return newItem;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_EventHandlerMaker.h
// LookinServer
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinEventHandler;
@interface LKS_EventHandlerMaker : NSObject
+ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,215 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_EventHandlerMaker.m
// LookinServer
//
// Created by Li Kai on 2019/8/7.
// https://lookin.work
//
#import "LKS_EventHandlerMaker.h"
#import "LookinTuple.h"
#import "LookinEventHandler.h"
#import "LookinObject.h"
#import "LookinWeakContainer.h"
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "LKS_GestureTargetActionsSearcher.h"
#import "LKS_MultiplatformAdapter.h"
@implementation LKS_EventHandlerMaker
+ (NSArray<LookinEventHandler *> *)makeForView:(UIView *)view {
if (!view) {
return nil;
}
NSMutableArray<LookinEventHandler *> *allHandlers = nil;
if ([view isKindOfClass:[UIControl class]]) {
NSArray<LookinEventHandler *> *targetActionHandlers = [self _targetActionHandlersForControl:(UIControl *)view];
if (targetActionHandlers.count) {
if (!allHandlers) {
allHandlers = [NSMutableArray array];
}
[allHandlers addObjectsFromArray:targetActionHandlers];
}
}
NSArray<LookinEventHandler *> *gestureHandlers = [self _gestureHandlersForView:view];
if (gestureHandlers.count) {
if (!allHandlers) {
allHandlers = [NSMutableArray array];
}
[allHandlers addObjectsFromArray:gestureHandlers];
}
return allHandlers.copy;
}
+ (NSArray<LookinEventHandler *> *)_gestureHandlersForView:(UIView *)view {
if (view.gestureRecognizers.count == 0) {
return nil;
}
NSArray<LookinEventHandler *> *handlers = [view.gestureRecognizers lookin_map:^id(NSUInteger idx, __kindof UIGestureRecognizer *recognizer) {
LookinEventHandler *handler = [LookinEventHandler new];
handler.handlerType = LookinEventHandlerTypeGesture;
handler.eventName = NSStringFromClass([recognizer class]);
NSArray<LookinTwoTuple *> *targetActionInfos = [LKS_GestureTargetActionsSearcher getTargetActionsFromRecognizer:recognizer];
handler.targetActions = [targetActionInfos lookin_map:^id(NSUInteger idx, LookinTwoTuple *rawTuple) {
NSObject *target = ((LookinWeakContainer *)rawTuple.first).object;
if (!target) {
// target
return nil;
}
LookinStringTwoTuple *newTuple = [LookinStringTwoTuple new];
newTuple.first = [LKS_Helper descriptionOfObject:target];
newTuple.second = (NSString *)rawTuple.second;
return newTuple;
}];
handler.inheritedRecognizerName = [self _inheritedRecognizerNameForRecognizer:recognizer];
handler.gestureRecognizerIsEnabled = recognizer.enabled;
if (recognizer.delegate) {
handler.gestureRecognizerDelegator = [LKS_Helper descriptionOfObject:recognizer.delegate];
}
handler.recognizerIvarTraces = [recognizer.lks_ivarTraces lookin_map:^id(NSUInteger idx, LookinIvarTrace *trace) {
return [NSString stringWithFormat:@"(%@ *) -> %@", trace.hostClassName, trace.ivarName];
}];
handler.recognizerOid = [recognizer lks_registerOid];
return handler;
}];
return handlers;
}
+ (NSString *)_inheritedRecognizerNameForRecognizer:(UIGestureRecognizer *)recognizer {
if (!recognizer) {
NSAssert(NO, @"");
return nil;
}
static NSArray<Class> *baseRecognizers;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// UIScreenEdgePanGestureRecognizer UIPanGestureRecognizer UIScreenEdgePanGestureRecognizer UIPanGestureRecognizer
#if TARGET_OS_TV
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UITapGestureRecognizer class]];
#elif TARGET_OS_VISION
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UIRotationGestureRecognizer class],
[UIPinchGestureRecognizer class],
[UITapGestureRecognizer class]];
#else
baseRecognizers = @[[UILongPressGestureRecognizer class],
[UIScreenEdgePanGestureRecognizer class],
[UIPanGestureRecognizer class],
[UISwipeGestureRecognizer class],
[UIRotationGestureRecognizer class],
[UIPinchGestureRecognizer class],
[UITapGestureRecognizer class]];
#endif
});
__block NSString *result = @"UIGestureRecognizer";
[baseRecognizers enumerateObjectsUsingBlock:^(Class _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([recognizer isMemberOfClass:obj]) {
// nil
result = nil;
*stop = YES;
return;
}
if ([recognizer isKindOfClass:obj]) {
result = NSStringFromClass(obj);
*stop = YES;
return;
}
}];
return result;
}
+ (NSArray<LookinEventHandler *> *)_targetActionHandlersForControl:(UIControl *)control {
static dispatch_once_t onceToken;
static NSArray<NSNumber *> *allEvents = nil;
dispatch_once(&onceToken,^{
allEvents = @[@(UIControlEventTouchDown), @(UIControlEventTouchDownRepeat), @(UIControlEventTouchDragInside), @(UIControlEventTouchDragOutside), @(UIControlEventTouchDragEnter), @(UIControlEventTouchDragExit), @(UIControlEventTouchUpInside), @(UIControlEventTouchUpOutside), @(UIControlEventTouchCancel), @(UIControlEventValueChanged), @(UIControlEventEditingDidBegin), @(UIControlEventEditingChanged), @(UIControlEventEditingDidEnd), @(UIControlEventEditingDidEndOnExit)];
if (@available(iOS 9.0, *)) {
allEvents = [allEvents arrayByAddingObject:@(UIControlEventPrimaryActionTriggered)];
}
});
NSSet *allTargets = control.allTargets;
if (!allTargets.count) {
return nil;
}
NSMutableArray<LookinEventHandler *> *handlers = [NSMutableArray array];
[allEvents enumerateObjectsUsingBlock:^(NSNumber * _Nonnull eventNum, NSUInteger idx, BOOL * _Nonnull stop) {
UIControlEvents event = [eventNum unsignedIntegerValue];
NSMutableArray<LookinStringTwoTuple *> *targetActions = [NSMutableArray array];
[allTargets enumerateObjectsUsingBlock:^(id _Nonnull target, BOOL * _Nonnull stop) {
NSArray<NSString *> *actions = [control actionsForTarget:target forControlEvent:event];
[actions enumerateObjectsUsingBlock:^(NSString * _Nonnull action, NSUInteger idx, BOOL * _Nonnull stop) {
LookinStringTwoTuple *tuple = [LookinStringTwoTuple new];
tuple.first = [LKS_Helper descriptionOfObject:target];
tuple.second = action;
[targetActions addObject:tuple];
}];
}];
if (targetActions.count) {
LookinEventHandler *handler = [LookinEventHandler new];
handler.handlerType = LookinEventHandlerTypeTargetAction;
handler.eventName = [self _nameFromControlEvent:event];
handler.targetActions = targetActions.copy;
[handlers addObject:handler];
}
}];
return handlers;
}
+ (NSString *)_nameFromControlEvent:(UIControlEvents)event {
static dispatch_once_t onceToken;
static NSDictionary<NSNumber *, NSString *> *eventsAndNames = nil;
dispatch_once(&onceToken,^{
NSMutableDictionary<NSNumber *, NSString *> *eventsAndNames_m = @{
@(UIControlEventTouchDown): @"UIControlEventTouchDown",
@(UIControlEventTouchDownRepeat): @"UIControlEventTouchDownRepeat",
@(UIControlEventTouchDragInside): @"UIControlEventTouchDragInside",
@(UIControlEventTouchDragOutside): @"UIControlEventTouchDragOutside",
@(UIControlEventTouchDragEnter): @"UIControlEventTouchDragEnter",
@(UIControlEventTouchDragExit): @"UIControlEventTouchDragExit",
@(UIControlEventTouchUpInside): @"UIControlEventTouchUpInside",
@(UIControlEventTouchUpOutside): @"UIControlEventTouchUpOutside",
@(UIControlEventTouchCancel): @"UIControlEventTouchCancel",
@(UIControlEventValueChanged): @"UIControlEventValueChanged",
@(UIControlEventEditingDidBegin): @"UIControlEventEditingDidBegin",
@(UIControlEventEditingChanged): @"UIControlEventEditingChanged",
@(UIControlEventEditingDidEnd): @"UIControlEventEditingDidEnd",
@(UIControlEventEditingDidEndOnExit): @"UIControlEventEditingDidEndOnExit",
}.mutableCopy;
if (@available(iOS 9.0, *)) {
eventsAndNames_m[@(UIControlEventPrimaryActionTriggered)] = @"UIControlEventPrimaryActionTriggered";
}
eventsAndNames = eventsAndNames_m.copy;
});
NSString *name = eventsAndNames[@(event)];
return name;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,21 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ExportManager.h
// LookinServer
//
// Created by Li Kai on 2019/5/13.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_ExportManager : NSObject
+ (instancetype)sharedInstance;
- (void)exportAndShare;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,193 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ExportManager.m
// LookinServer
//
// Created by Li Kai on 2019/5/13.
// https://lookin.work
//
#import "LKS_ExportManager.h"
#import "UIViewController+LookinServer.h"
#import "LookinHierarchyInfo.h"
#import "LookinHierarchyFile.h"
#import "LookinAppInfo.h"
#import "LookinServerDefines.h"
#import "LKS_MultiplatformAdapter.h"
@interface LKS_ExportManagerMaskView : UIView
@property(nonatomic, strong) UIView *tipsView;
@property(nonatomic, strong) UILabel *firstLabel;
@property(nonatomic, strong) UILabel *secondLabel;
@property(nonatomic, strong) UILabel *thirdLabel;
@end
@implementation LKS_ExportManagerMaskView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.35];
self.tipsView = [UIView new];
self.tipsView.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:.88];
self.tipsView.layer.cornerRadius = 6;
self.tipsView.layer.masksToBounds = YES;
[self addSubview:self.tipsView];
self.firstLabel = [UILabel new];
self.firstLabel.text = LKS_Localized(@"Creating File…");
self.firstLabel.textColor = [UIColor whiteColor];
self.firstLabel.font = [UIFont boldSystemFontOfSize:14];
self.firstLabel.textAlignment = NSTextAlignmentCenter;
self.firstLabel.numberOfLines = 0;
[self.tipsView addSubview:self.firstLabel];
self.secondLabel = [UILabel new];
self.secondLabel.text = LKS_Localized(@"May take 8 or more seconds according to the UI complexity.");
self.secondLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
self.secondLabel.font = [UIFont systemFontOfSize:12];
self.secondLabel.textAlignment = NSTextAlignmentLeft;
self.secondLabel.numberOfLines = 0;
[self.tipsView addSubview:self.secondLabel];
self.thirdLabel = [UILabel new];
self.thirdLabel.text = LKS_Localized(@"The file can be opend by Lookin.app in macOS.");
self.thirdLabel.textColor = [UIColor colorWithRed:173/255.0 green:180/255.0 blue:190/255.0 alpha:1];
self.thirdLabel.font = [UIFont systemFontOfSize:12];
self.thirdLabel.textAlignment = NSTextAlignmentCenter;
self.thirdLabel.numberOfLines = 0;
[self.tipsView addSubview:self.thirdLabel];
}
return self;
}
- (void)layoutSubviews {
[super layoutSubviews];
UIEdgeInsets insets = UIEdgeInsetsMake(8, 10, 8, 10);
CGFloat maxLabelWidth = self.bounds.size.width * .8 - insets.left - insets.right;
CGSize firstSize = [self.firstLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGSize secondSize = [self.secondLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGSize thirdSize = [self.thirdLabel sizeThatFits:CGSizeMake(maxLabelWidth, CGFLOAT_MAX)];
CGFloat tipsWidth = MAX(MAX(firstSize.width, secondSize.width), thirdSize.width) + insets.left + insets.right;
self.firstLabel.frame = CGRectMake(tipsWidth / 2.0 - firstSize.width / 2.0, insets.top, firstSize.width, firstSize.height);
self.secondLabel.frame = CGRectMake(tipsWidth / 2.0 - secondSize.width / 2.0, CGRectGetMaxY(self.firstLabel.frame) + 10, secondSize.width, secondSize.height);
self.thirdLabel.frame = CGRectMake(tipsWidth / 2.0 - thirdSize.width / 2.0, CGRectGetMaxY(self.secondLabel.frame) + 5, thirdSize.width, thirdSize.height);
self.tipsView.frame = ({
CGFloat height = CGRectGetMaxY(self.thirdLabel.frame) + insets.bottom;
CGRectMake(self.bounds.size.width / 2.0 - tipsWidth / 2.0, self.bounds.size.height / 2.0 - height / 2.0, tipsWidth, height);
});
}
@end
@interface LKS_ExportManager ()
#if TARGET_OS_TV
#else
@property(nonatomic, strong) UIDocumentInteractionController *documentController;
#endif
@property(nonatomic, strong) LKS_ExportManagerMaskView *maskView;
@end
@implementation LKS_ExportManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_ExportManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
#if TARGET_OS_TV
- (void)exportAndShare {
NSAssert(NO, @"not supported");
}
#else
- (void)exportAndShare {
UIViewController *visibleVc = [UIViewController lks_visibleViewController];
if (!visibleVc) {
NSLog(@"LookinServer - Failed to export because we didn't find any visible view controller.");
return;
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_WillExport" object:nil];
if (!self.maskView) {
self.maskView = [LKS_ExportManagerMaskView new];
}
[visibleVc.view.window addSubview:self.maskView];
self.maskView.frame = visibleVc.view.window.bounds;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
LookinHierarchyInfo *info = [LookinHierarchyInfo exportedInfo];
LookinHierarchyFile *file = [LookinHierarchyFile new];
file.serverVersion = info.serverVersion;
file.hierarchyInfo = info;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:file];
if (!data) {
return;
}
NSString *fileName = ({
NSString *timeString = ({
NSDate *date = [NSDate date];
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMddHHmm"];
[formatter stringFromDate:date];
});
NSString *iOSVersion = ({
NSString *str = info.appInfo.osDescription;
NSUInteger dotIdx = [str rangeOfString:@"."].location;
if (dotIdx != NSNotFound) {
str = [str substringToIndex:dotIdx];
}
str;
});
[NSString stringWithFormat:@"%@_ios%@_%@.lookin", info.appInfo.appName, iOSVersion, timeString];
});
NSString *path = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), fileName];
[data writeToFile:path atomically:YES];
[self.maskView removeFromSuperview];
if (!self.documentController) {
self.documentController = [UIDocumentInteractionController new];
}
self.documentController.URL = [NSURL fileURLWithPath:path];
if ([LKS_MultiplatformAdapter isiPad]) {
[self.documentController presentOpenInMenuFromRect:CGRectMake(0, 0, 1, 1) inView:visibleVc.view animated:YES];
} else {
[self.documentController presentOpenInMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
}
[[NSNotificationCenter defaultCenter] postNotificationName:@"Lookin_DidFinishExport" object:nil];
// [self.documentController presentOptionsMenuFromRect:visibleVc.view.bounds inView:visibleVc.view animated:YES];
// CFTimeInterval endTime = CACurrentMediaTime();
// CFTimeInterval consumingTime = endTime - startTime;
// NSLog(@"LookinServer - 导出 UI 结构耗时:%@", @(consumingTime));
});
}
#endif
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_GestureTargetActionsSearcher.h
// LookinServer
//
// Created by likai.123 on 2023/9/11.
//
#import <UIKit/UIKit.h>
@class LookinTwoTuple;
NS_ASSUME_NONNULL_BEGIN
@interface LKS_GestureTargetActionsSearcher : NSObject
/// 返回一个 UIGestureRecognizer 实例身上绑定的 target & action 信息
/// tuple.first => LookinWeakContainer(包裹着 target)tuple.second => action(方法名字符串)
+ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,52 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_GestureTargetActionsSearcher.m
// LookinServer
//
// Created by likai.123 on 2023/9/11.
//
#import "LKS_GestureTargetActionsSearcher.h"
#import <objc/runtime.h>
#import "NSArray+Lookin.h"
#import "LookinTuple.h"
#import "LookinWeakContainer.h"
@implementation LKS_GestureTargetActionsSearcher
+ (NSArray<LookinTwoTuple *> *)getTargetActionsFromRecognizer:(UIGestureRecognizer *)recognizer {
if (!recognizer) {
return @[];
}
// KVC try catch Crash
@try {
NSArray* targetsList = [recognizer valueForKey:@"_targets"];
if (!targetsList || targetsList.count == 0) {
return @[];
}
// UIGestureRecognizerTarget*
// _target _action SEL
NSArray<LookinTwoTuple *>* ret = [targetsList lookin_map:^id(NSUInteger idx, id targetBox) {
id targetObj = [targetBox valueForKey:@"_target"];
if (!targetObj) {
return nil;
}
SEL action = ((SEL (*)(id, Ivar))object_getIvar)(targetBox, class_getInstanceVariable([targetBox class], "_action"));
LookinTwoTuple* tuple = [LookinTwoTuple new];
tuple.first = [LookinWeakContainer containerWithObject:targetObj];
tuple.second = (action == NULL ? @"NULL" : NSStringFromSelector(action));
return tuple;
}];
return ret;
}
@catch (NSException * e) {
NSLog(@"LookinServer - %@", e);
return @[];
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,29 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_Helper.h
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#define LKS_Localized(stringKey) NSLocalizedStringFromTableInBundle(stringKey, nil, [NSBundle bundleForClass:self.class], nil)
@interface LKS_Helper : NSObject
/// 如果 object 为 nil 则返回字符串 “nil”否则返回字符串格式类似于 (UIView *)
+ (NSString *)descriptionOfObject:(id)object;
/// 返回当前的bundle
+ (NSBundle *)bundle;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,38 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_Helper.m
// LookinServer
//
// Created by Li Kai on 2019/7/20.
// https://lookin.work
//
#import "LKS_Helper.h"
#import "NSObject+LookinServer.h"
@implementation LKS_Helper
+ (NSString *)descriptionOfObject:(id)object {
if (!object) {
return @"nil";
}
NSString *className = NSStringFromClass([object class]);
return [NSString stringWithFormat:@"(%@ *)", className];
}
+ (NSBundle *)bundle {
static id bundle = nil;
if (bundle != nil) {
#ifdef SPM_RESOURCE_BUNDLE_IDENTIFITER
bundle = [NSBundle bundleWithIdentifier:SPM_RESOURCE_BUNDLE_IDENTIFITER];
#else
bundle = [NSBundle bundleForClass:self.class];
#endif
}
return bundle;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,31 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDisplayItemsMaker.h
// LookinServer
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LookinDefines.h"
@class LookinDisplayItem;
@interface LKS_HierarchyDisplayItemsMaker : NSObject
/// @param hasScreenshots 是否包含 soloScreenshots 和 groupScreenshot 属性
/// @param hasAttrList 是否包含 attributesGroupList 属性
/// @param lowQuality screenshots 是否为低质量(当 hasScreenshots 为 NO 时,该属性无意义)
/// @param readCustomInfo 是否读取 lookin_customDebugInfos比如低版本的 Lookin 发请求时,就无需读取(因为 Lookin 解析不了、还可能出 Bug
/// @param saveCustomSetter 是否要读取并保存用户给 attribute 配置的 custom setter
+ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter;
/// 把 layer 的 sublayers 转换为 displayItem 数组并返回
+ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,162 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_HierarchyDisplayItemsMaker.m
// LookinServer
//
// Created by Li Kai on 2019/2/19.
// https://lookin.work
//
#import "LKS_HierarchyDisplayItemsMaker.h"
#import "LookinDisplayItem.h"
#import "LKS_TraceManager.h"
#import "LKS_AttrGroupsMaker.h"
#import "LKS_EventHandlerMaker.h"
#import "LookinServerDefines.h"
#import "UIColor+LookinServer.h"
#import "LKSConfigManager.h"
#import "LKS_CustomAttrGroupsMaker.h"
#import "LKS_CustomDisplayItemsMaker.h"
#import "LKS_CustomAttrSetterManager.h"
#import "LKS_MultiplatformAdapter.h"
@implementation LKS_HierarchyDisplayItemsMaker
+ (NSArray<LookinDisplayItem *> *)itemsWithScreenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
[[LKS_TraceManager sharedInstance] reload];
NSArray<UIWindow *> *windows = [LKS_MultiplatformAdapter allWindows];
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:windows.count];
[windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *item = [self _displayItemWithLayer:window.layer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
item.representedAsKeyWindow = window.isKeyWindow;
if (item) {
[resultArray addObject:item];
}
}];
return [resultArray copy];
}
+ (LookinDisplayItem *)_displayItemWithLayer:(CALayer *)layer screenshots:(BOOL)hasScreenshots attrList:(BOOL)hasAttrList lowImageQuality:(BOOL)lowQuality readCustomInfo:(BOOL)readCustomInfo saveCustomSetter:(BOOL)saveCustomSetter {
if (!layer) {
return nil;
}
LookinDisplayItem *item = [LookinDisplayItem new];
CGRect layerFrame = layer.frame;
UIView *hostView = layer.lks_hostView;
if (hostView && hostView.superview) {
layerFrame = [hostView.superview convertRect:layerFrame toView:nil];
}
if ([self validateFrame:layerFrame]) {
item.frame = layer.frame;
} else {
NSLog(@"LookinServer - The layer frame(%@) seems really weird. Lookin will ignore it to avoid potential render error in Lookin.", NSStringFromCGRect(layer.frame));
item.frame = CGRectZero;
}
item.bounds = layer.bounds;
if (hasScreenshots) {
item.soloScreenshot = [layer lks_soloScreenshotWithLowQuality:lowQuality];
item.groupScreenshot = [layer lks_groupScreenshotWithLowQuality:lowQuality];
item.screenshotEncodeType = LookinDisplayItemImageEncodeTypeNSData;
}
if (hasAttrList) {
item.attributesGroupList = [LKS_AttrGroupsMaker attrGroupsForLayer:layer];
LKS_CustomAttrGroupsMaker *maker = [[LKS_CustomAttrGroupsMaker alloc] initWithLayer:layer];
[maker execute];
item.customAttrGroupList = [maker getGroups];
item.customDisplayTitle = [maker getCustomDisplayTitle];
item.danceuiSource = [maker getDanceUISource];
}
item.isHidden = layer.isHidden;
item.alpha = layer.opacity;
item.layerObject = [LookinObject instanceWithObject:layer];
item.shouldCaptureImage = [LKSConfigManager shouldCaptureScreenshotOfLayer:layer];
if (layer.lks_hostView) {
UIView *view = layer.lks_hostView;
item.viewObject = [LookinObject instanceWithObject:view];
item.eventHandlers = [LKS_EventHandlerMaker makeForView:view];
item.backgroundColor = view.backgroundColor;
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
item.hostViewControllerObject = [LookinObject instanceWithObject:vc];
}
} else {
item.backgroundColor = [UIColor lks_colorWithCGColor:layer.backgroundColor];
}
if (layer.sublayers.count) {
NSArray<CALayer *> *sublayers = [layer.sublayers copy];
NSMutableArray<LookinDisplayItem *> *allSubitems = [NSMutableArray arrayWithCapacity:sublayers.count];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:hasScreenshots attrList:hasAttrList lowImageQuality:lowQuality readCustomInfo:readCustomInfo saveCustomSetter:saveCustomSetter];
if (sublayer_item) {
[allSubitems addObject:sublayer_item];
}
}];
item.subitems = [allSubitems copy];
}
if (readCustomInfo) {
NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:saveCustomSetter] make];
if (customSubitems.count > 0) {
if (item.subitems) {
item.subitems = [item.subitems arrayByAddingObjectsFromArray:customSubitems];
} else {
item.subitems = customSubitems;
}
}
}
return item;
}
+ (NSArray<LookinDisplayItem *> *)subitemsOfLayer:(CALayer *)layer {
if (!layer || layer.sublayers.count == 0) {
return @[];
}
[[LKS_TraceManager sharedInstance] reload];
NSMutableArray<LookinDisplayItem *> *resultSubitems = [NSMutableArray array];
NSArray<CALayer *> *sublayers = [layer.sublayers copy];
[sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
LookinDisplayItem *sublayer_item = [self _displayItemWithLayer:sublayer screenshots:NO attrList:NO lowImageQuality:NO readCustomInfo:YES saveCustomSetter:YES];
if (sublayer_item) {
[resultSubitems addObject:sublayer_item];
}
}];
NSArray<LookinDisplayItem *> *customSubitems = [[[LKS_CustomDisplayItemsMaker alloc] initWithLayer:layer saveAttrSetter:YES] make];
if (customSubitems.count > 0) {
[resultSubitems addObjectsFromArray:customSubitems];
}
return resultSubitems;
}
+ (BOOL)validateFrame:(CGRect)frame {
return !CGRectIsNull(frame) && !CGRectIsInfinite(frame) && ![self cgRectIsNaN:frame] && ![self cgRectIsInf:frame] && ![self cgRectIsUnreasonable:frame];
}
+ (BOOL)cgRectIsNaN:(CGRect)rect {
return isnan(rect.origin.x) || isnan(rect.origin.y) || isnan(rect.size.width) || isnan(rect.size.height);
}
+ (BOOL)cgRectIsInf:(CGRect)rect {
return isinf(rect.origin.x) || isinf(rect.origin.y) || isinf(rect.size.width) || isinf(rect.size.height);
}
+ (BOOL)cgRectIsUnreasonable:(CGRect)rect {
return ABS(rect.origin.x) > 100000 || ABS(rect.origin.y) > 100000 || rect.size.width < 0 || rect.size.height < 0 || rect.size.width > 100000 || rect.size.height > 100000;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,30 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_MultiplatformAdapter.h
//
//
// Created by nixjiang on 2024/3/12.
//
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface LKS_MultiplatformAdapter : NSObject
+ (UIWindow *)keyWindow;
+ (NSArray<UIWindow *> *)allWindows;
+ (CGRect)mainScreenBounds;
+ (CGFloat)mainScreenScale;
+ (BOOL)isiPad;
@end
NS_ASSUME_NONNULL_END
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,92 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_MultiplatformAdapter.m
//
//
// Created by nixjiang on 2024/3/12.
//
#import "LKS_MultiplatformAdapter.h"
#import <UIKit/UIKit.h>
@implementation LKS_MultiplatformAdapter
+ (BOOL)isiPad {
static BOOL s_isiPad = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *nsModel = [UIDevice currentDevice].model;
s_isiPad = [nsModel hasPrefix:@"iPad"];
});
return s_isiPad;
}
+ (CGRect)mainScreenBounds {
#if TARGET_OS_VISION
return [LKS_MultiplatformAdapter getFirstActiveWindowScene].coordinateSpace.bounds;
#else
return [UIScreen mainScreen].bounds;
#endif
}
+ (CGFloat)mainScreenScale {
#if TARGET_OS_VISION
return 2.f;
#else
return [UIScreen mainScreen].scale;
#endif
}
#if TARGET_OS_VISION
+ (UIWindowScene *)getFirstActiveWindowScene {
for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
if (windowScene.activationState == UISceneActivationStateForegroundActive) {
return windowScene;
}
}
return nil;
}
#endif
+ (UIWindow *)keyWindow {
#if TARGET_OS_VISION
return [self getFirstActiveWindowScene].keyWindow;
#else
return [UIApplication sharedApplication].keyWindow;
#endif
}
+ (NSArray<UIWindow *> *)allWindows {
#if TARGET_OS_VISION
NSMutableArray<UIWindow *> *windows = [NSMutableArray new];
for (UIScene *scene in
UIApplication.sharedApplication.connectedScenes) {
if (![scene isKindOfClass:UIWindowScene.class]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
[windows addObjectsFromArray:windowScene.windows];
// UIModalPresentationFormSheetwindowscene.windowsscene.keyWindow
if (![windows containsObject:windowScene.keyWindow]) {
if (![NSStringFromClass(windowScene.keyWindow.class) containsString:@"HUD"]) {
[windows addObject:windowScene.keyWindow];
}
}
}
return [windows copy];
#else
return [[UIApplication sharedApplication].windows copy];
#endif
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ObjectRegistry.h
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@interface LKS_ObjectRegistry : NSObject
+ (instancetype)sharedInstance;
- (unsigned long)addObject:(NSObject *)object;
- (NSObject *)objectWithOid:(unsigned long)oid;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,62 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_ObjectRegistry.m
// LookinServer
//
// Created by Li Kai on 2019/4/21.
// https://lookin.work
//
#import "LKS_ObjectRegistry.h"
#import <objc/runtime.h>
@interface LKS_ObjectRegistry ()
@property(nonatomic, strong) NSPointerArray *data;
@end
@implementation LKS_ObjectRegistry
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_ObjectRegistry *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone{
return [self sharedInstance];
}
- (instancetype)init {
if (self = [super init]) {
self.data = [NSPointerArray weakObjectsPointerArray];
// index 0 Null
self.data.count = 1;
}
return self;
}
- (unsigned long)addObject:(NSObject *)object {
if (!object) {
return 0;
}
[self.data addPointer:(void *)object];
return self.data.count - 1;
}
- (NSObject *)objectWithOid:(unsigned long)oid {
if (self.data.count <= oid) {
return nil;
}
id object = [self.data pointerAtIndex:oid];
return object;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_TraceManager.h
// LookinServer
//
// Created by Li Kai on 2019/5/5.
// https://lookin.work
//
#import <Foundation/Foundation.h>
@class LookinIvarTrace;
@interface LKS_TraceManager : NSObject
+ (instancetype)sharedInstance;
- (void)reload;
- (void)addSearchTarger:(id)target;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,310 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LKS_TraceManager.m
// LookinServer
//
// Created by Li Kai on 2019/5/5.
// https://lookin.work
//
#import "LKS_TraceManager.h"
#import <objc/runtime.h>
#import "LookinIvarTrace.h"
#import "LookinServerDefines.h"
#import "LookinWeakContainer.h"
#import "LKS_MultiplatformAdapter.h"
#ifdef LOOKIN_SERVER_SWIFT_ENABLED
#if __has_include(<LookinServer/LookinServer-Swift.h>)
#import <LookinServer/LookinServer-Swift.h>
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#elif __has_include("LookinServer-Swift.h")
#import "LookinServer-Swift.h"
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#endif
#endif
#ifdef SPM_LOOKIN_SERVER_ENABLED
@import LookinServerSwift;
#define LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
#endif
@interface LKS_TraceManager ()
@property(nonatomic, strong) NSMutableArray<LookinWeakContainer *> *searchTargets;
@end
@implementation LKS_TraceManager
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
static LKS_TraceManager *instance = nil;
dispatch_once(&onceToken,^{
instance = [[super allocWithZone:NULL] init];
});
return instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
return [self sharedInstance];
}
- (void)addSearchTarger:(id)target {
if (!target) {
return;
}
if (!self.searchTargets) {
self.searchTargets = [NSMutableArray array];
}
LookinWeakContainer *container = [LookinWeakContainer containerWithObject:target];
[self.searchTargets addObject:container];
}
- (void)reload {
//
[NSObject lks_clearAllObjectsTraces];
[self.searchTargets enumerateObjectsUsingBlock:^(LookinWeakContainer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (!obj.object) {
return;
}
[self _markIVarsInAllClassLevelsOfObject:obj.object];
}];
[[LKS_MultiplatformAdapter allWindows] enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) {
[self _addTraceForLayersRootedByLayer:window.layer];
}];
}
- (void)_addTraceForLayersRootedByLayer:(CALayer *)layer {
UIView *view = layer.lks_hostView;
if ([view.superview lks_isChildrenViewOfTabBar]) {
view.lks_isChildrenViewOfTabBar = YES;
} else if ([view isKindOfClass:[UITabBar class]]) {
view.lks_isChildrenViewOfTabBar = YES;
}
if (view) {
[self _markIVarsInAllClassLevelsOfObject:view];
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
[self _markIVarsInAllClassLevelsOfObject:vc];
}
[self _buildSpecialTraceForView:view];
} else {
[self _markIVarsInAllClassLevelsOfObject:layer];
}
[[layer.sublayers copy] enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull sublayer, NSUInteger idx, BOOL * _Nonnull stop) {
[self _addTraceForLayersRootedByLayer:sublayer];
}];
}
- (void)_buildSpecialTraceForView:(UIView *)view {
UIViewController* vc = [view lks_findHostViewController];
if (vc) {
view.lks_specialTrace = [NSString stringWithFormat:@"%@.view", NSStringFromClass(vc.class)];
} else if ([view isKindOfClass:[UIWindow class]]) {
CGFloat currentWindowLevel = ((UIWindow *)view).windowLevel;
if (((UIWindow *)view).isKeyWindow) {
view.lks_specialTrace = [NSString stringWithFormat:@"KeyWindow ( Level: %@ )", @(currentWindowLevel)];
} else {
view.lks_specialTrace = [NSString stringWithFormat:@"WindowLevel: %@", @(currentWindowLevel)];
}
} else if ([view isKindOfClass:[UITableViewCell class]]) {
((UITableViewCell *)view).backgroundView.lks_specialTrace = @"cell.backgroundView";
((UITableViewCell *)view).accessoryView.lks_specialTrace = @"cell.accessoryView";
} else if ([view isKindOfClass:[UITableView class]]) {
UITableView *tableView = (UITableView *)view;
NSMutableArray<NSNumber *> *relatedSectionIdx = [NSMutableArray array];
[[tableView visibleCells] enumerateObjectsUsingBlock:^(__kindof UITableViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *indexPath = [tableView indexPathForCell:cell];
cell.lks_specialTrace = [NSString stringWithFormat:@"{ sec:%@, row:%@ }", @(indexPath.section), @(indexPath.row)];
if (![relatedSectionIdx containsObject:@(indexPath.section)]) {
[relatedSectionIdx addObject:@(indexPath.section)];
}
}];
[relatedSectionIdx enumerateObjectsUsingBlock:^(NSNumber * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSUInteger secIdx = [obj unsignedIntegerValue];
UIView *secHeaderView = [tableView headerViewForSection:secIdx];
secHeaderView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec: %@ }", @(secIdx)];
UIView *secFooterView = [tableView footerViewForSection:secIdx];
secFooterView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec: %@ }", @(secIdx)];
}];
} else if ([view isKindOfClass:[UICollectionView class]]) {
UICollectionView *collectionView = (UICollectionView *)view;
collectionView.backgroundView.lks_specialTrace = @"collectionView.backgroundView";
if (@available(iOS 9.0, *)) {
[[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionHeader] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *headerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionHeader atIndexPath:indexPath];
headerView.lks_specialTrace = [NSString stringWithFormat:@"sectionHeader { sec:%@ }", @(indexPath.section)];
}];
[[collectionView indexPathsForVisibleSupplementaryElementsOfKind:UICollectionElementKindSectionFooter] enumerateObjectsUsingBlock:^(NSIndexPath * _Nonnull indexPath, NSUInteger idx, BOOL * _Nonnull stop) {
UIView *footerView = [collectionView supplementaryViewForElementKind:UICollectionElementKindSectionFooter atIndexPath:indexPath];
footerView.lks_specialTrace = [NSString stringWithFormat:@"sectionFooter { sec:%@ }", @(indexPath.section)];
}];
}
[[collectionView visibleCells] enumerateObjectsUsingBlock:^(__kindof UICollectionViewCell * _Nonnull cell, NSUInteger idx, BOOL * _Nonnull stop) {
NSIndexPath *indexPath = [collectionView indexPathForCell:cell];
cell.lks_specialTrace = [NSString stringWithFormat:@"{ item:%@, sec:%@ }", @(indexPath.item), @(indexPath.section)];
}];
} else if ([view isKindOfClass:[UITableViewHeaderFooterView class]]) {
UITableViewHeaderFooterView *headerFooterView = (UITableViewHeaderFooterView *)view;
headerFooterView.textLabel.lks_specialTrace = @"sectionHeaderFooter.textLabel";
headerFooterView.detailTextLabel.lks_specialTrace = @"sectionHeaderFooter.detailTextLabel";
}
}
- (void)_markIVarsInAllClassLevelsOfObject:(NSObject *)object {
[self _markIVarsOfObject:object class:object.class];
#ifdef LOOKIN_SERVER_SWIFT_ENABLED_SUCCESSFULLY
[LKS_SwiftTraceManager swiftMarkIVarsOfObject:object];
#endif
}
- (void)_markIVarsOfObject:(NSObject *)hostObject class:(Class)targetClass {
if (!targetClass) {
return;
}
NSArray<NSString *> *prefixesToTerminateRecursion = @[@"NSObject", @"UIResponder", @"UIButton", @"UIButtonLabel"];
BOOL hasPrefix = [prefixesToTerminateRecursion lookin_any:^BOOL(NSString *prefix) {
return [NSStringFromClass(targetClass) hasPrefix:prefix];
}];
if (hasPrefix) {
return;
}
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList(targetClass, &outCount);
for (unsigned int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString *ivarType = [[NSString alloc] lookin_safeInitWithUTF8String:ivar_getTypeEncoding(ivar)];
if (![ivarType hasPrefix:@"@"] || ivarType.length <= 3) {
continue;
}
NSString *ivarClassName = [ivarType substringWithRange:NSMakeRange(2, ivarType.length - 3)];
Class ivarClass = NSClassFromString(ivarClassName);
if (![ivarClass isSubclassOfClass:[UIView class]]
&& ![ivarClass isSubclassOfClass:[CALayer class]]
&& ![ivarClass isSubclassOfClass:[UIViewController class]]
&& ![ivarClass isSubclassOfClass:[UIGestureRecognizer class]]) {
continue;
}
const char * ivarNameChar = ivar_getName(ivar);
if (!ivarNameChar) {
continue;
}
// ivarObject UIView, CALayer, UIViewController, UIGestureRecognizer
NSObject *ivarObject = object_getIvar(hostObject, ivar);
if (!ivarObject || ![ivarObject isKindOfClass:[NSObject class]]) {
continue;
}
LookinIvarTrace *ivarTrace = [LookinIvarTrace new];
ivarTrace.hostObject = hostObject;
ivarTrace.hostClassName = [self makeDisplayClassNameWithSuper:targetClass childClass:hostObject.class];
ivarTrace.ivarName = [[NSString alloc] lookin_safeInitWithUTF8String:ivarNameChar];
if (hostObject == ivarObject) {
ivarTrace.relation = LookinIvarTraceRelationValue_Self;
} else if ([hostObject isKindOfClass:[UIView class]]) {
CALayer *ivarLayer = nil;
if ([ivarObject isKindOfClass:[CALayer class]]) {
ivarLayer = (CALayer *)ivarObject;
} else if ([ivarObject isKindOfClass:[UIView class]]) {
ivarLayer = ((UIView *)ivarObject).layer;
}
if (ivarLayer && (ivarLayer.superlayer == ((UIView *)hostObject).layer)) {
ivarTrace.relation = @"superview";
}
}
if ([LKS_InvalidIvarTraces() containsObject:ivarTrace]) {
continue;
}
if (![ivarObject respondsToSelector:@selector(lks_ivarTraces)] || ![ivarObject respondsToSelector:@selector(setLks_ivarTraces:)]) {
continue;
}
if (!ivarObject.lks_ivarTraces) {
ivarObject.lks_ivarTraces = [NSArray array];
}
if (![ivarObject.lks_ivarTraces containsObject:ivarTrace]) {
ivarObject.lks_ivarTraces = [ivarObject.lks_ivarTraces arrayByAddingObject:ivarTrace];
}
}
free(ivars);
Class superClass = [targetClass superclass];
[self _markIVarsOfObject:hostObject class:superClass];
}
// superClass UIView childClass UIButton
- (NSString *)makeDisplayClassNameWithSuper:(Class)superClass childClass:(Class)childClass {
NSString *superName = NSStringFromClass(superClass);
if (!childClass) {
return superName;
}
NSString *childName = NSStringFromClass(childClass);
if ([childName isEqualToString:superName]) {
return superName;
}
return [NSString stringWithFormat:@"%@ : %@", childName, superName];
}
static NSSet<LookinIvarTrace *> *LKS_InvalidIvarTraces(void) {
static NSSet *list;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSMutableSet *set = [NSMutableSet set];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIView";
trace.ivarName = @"_window";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIViewController";
trace.ivarName = @"_view";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIView";
trace.ivarName = @"_viewDelegate";
trace;
})];
[set addObject:({
LookinIvarTrace *trace = [LookinIvarTrace new];
trace.hostClassName = @"UIViewController";
trace.ivarName = @"_parentViewController";
trace;
})];
list = set.copy;
});
return list;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinServer_PrefixHeader.pch
// LookinServer
//
// Created by Li Kai on 2018/12/21.
// https://lookin.work
//
#import "TargetConditionals.h"
#import "LookinDefines.h"
#import "LKS_Helper.h"
#import "NSObject+LookinServer.h"
#import "NSArray+Lookin.h"
#import "NSSet+Lookin.h"
#import "CALayer+Lookin.h"
#import "UIView+LookinServer.h"
#import "CALayer+LookinServer.h"
#import "NSObject+Lookin.h"
#import "NSString+Lookin.h"
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,23 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// CALayer+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/8/4.
// https://lookin.work
//
#import "LookinDefines.h"
#import <QuartzCore/QuartzCore.h>
@interface CALayer (Lookin)
- (void)lookin_removeImplicitAnimations;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,70 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// CALayer+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/8/4.
// https://lookin.work
//
#import "CALayer+Lookin.h"
@implementation CALayer (Lookin)
- (void)lookin_removeImplicitAnimations {
NSMutableDictionary<NSString *, id<CAAction>> *actions = @{NSStringFromSelector(@selector(bounds)): [NSNull null],
NSStringFromSelector(@selector(position)): [NSNull null],
NSStringFromSelector(@selector(zPosition)): [NSNull null],
NSStringFromSelector(@selector(anchorPoint)): [NSNull null],
NSStringFromSelector(@selector(anchorPointZ)): [NSNull null],
NSStringFromSelector(@selector(transform)): [NSNull null],
NSStringFromSelector(@selector(sublayerTransform)): [NSNull null],
NSStringFromSelector(@selector(masksToBounds)): [NSNull null],
NSStringFromSelector(@selector(contents)): [NSNull null],
NSStringFromSelector(@selector(contentsRect)): [NSNull null],
NSStringFromSelector(@selector(contentsScale)): [NSNull null],
NSStringFromSelector(@selector(contentsCenter)): [NSNull null],
NSStringFromSelector(@selector(minificationFilterBias)): [NSNull null],
NSStringFromSelector(@selector(backgroundColor)): [NSNull null],
NSStringFromSelector(@selector(cornerRadius)): [NSNull null],
NSStringFromSelector(@selector(borderWidth)): [NSNull null],
NSStringFromSelector(@selector(borderColor)): [NSNull null],
NSStringFromSelector(@selector(opacity)): [NSNull null],
NSStringFromSelector(@selector(compositingFilter)): [NSNull null],
NSStringFromSelector(@selector(filters)): [NSNull null],
NSStringFromSelector(@selector(backgroundFilters)): [NSNull null],
NSStringFromSelector(@selector(shouldRasterize)): [NSNull null],
NSStringFromSelector(@selector(rasterizationScale)): [NSNull null],
NSStringFromSelector(@selector(shadowColor)): [NSNull null],
NSStringFromSelector(@selector(shadowOpacity)): [NSNull null],
NSStringFromSelector(@selector(shadowOffset)): [NSNull null],
NSStringFromSelector(@selector(shadowRadius)): [NSNull null],
NSStringFromSelector(@selector(shadowPath)): [NSNull null]}.mutableCopy;
if ([self isKindOfClass:[CAShapeLayer class]]) {
[actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(path)): [NSNull null],
NSStringFromSelector(@selector(fillColor)): [NSNull null],
NSStringFromSelector(@selector(strokeColor)): [NSNull null],
NSStringFromSelector(@selector(strokeStart)): [NSNull null],
NSStringFromSelector(@selector(strokeEnd)): [NSNull null],
NSStringFromSelector(@selector(lineWidth)): [NSNull null],
NSStringFromSelector(@selector(miterLimit)): [NSNull null],
NSStringFromSelector(@selector(lineDashPhase)): [NSNull null]}];
}
if ([self isKindOfClass:[CAGradientLayer class]]) {
[actions addEntriesFromDictionary:@{NSStringFromSelector(@selector(colors)): [NSNull null],
NSStringFromSelector(@selector(locations)): [NSNull null],
NSStringFromSelector(@selector(startPoint)): [NSNull null],
NSStringFromSelector(@selector(endPoint)): [NSNull null]}];
}
self.actions = actions;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,27 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Color+Lookin.h
// LookinShared
//
// Created by 李凯 on 2022/4/2.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@interface NSColor (Lookin)
+ (instancetype)lookin_colorFromRGBAComponents:(NSArray<NSNumber *> *)components;
- (NSArray<NSNumber *> *)lookin_rgbaComponents;
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,42 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Color+Lookin.m
// LookinShared
//
// Created by on 2022/4/2.
//
#import "Image+Lookin.h"
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@implementation NSColor (Lookin)
+ (instancetype)lookin_colorFromRGBAComponents:(NSArray<NSNumber *> *)components {
if (!components) {
return nil;
}
if (components.count != 4) {
NSAssert(NO, @"");
return nil;
}
NSColor *color = [NSColor colorWithRed:components[0].doubleValue green:components[1].doubleValue blue:components[2].doubleValue alpha:components[3].doubleValue];
return color;
}
- (NSArray<NSNumber *> *)lookin_rgbaComponents {
NSColor *rgbColor = [self colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
CGFloat r, g, b, a;
[rgbColor getRed:&r green:&g blue:&b alpha:&a];
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
}
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,25 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Image+Lookin.h
// LookinShared
//
// Created by 李凯 on 2022/4/2.
//
#import <Foundation/Foundation.h>
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@interface NSImage (LookinClient)
- (NSData *)lookin_data;
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,26 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Image+Lookin.m
// LookinShared
//
// Created by on 2022/4/2.
//
#import "Image+Lookin.h"
#if TARGET_OS_IPHONE
#elif TARGET_OS_MAC
@implementation NSImage (LookinClient)
- (NSData *)lookin_data {
return [self TIFFRepresentation];
}
@end
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,72 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSArray+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/9/3.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
@interface NSArray<__covariant ValueType> (Lookin)
/**
初始化一个新的 NSArray 并返回,新数组的长度为 count如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则会舍弃多余的元素,被舍弃的元素会作为参数传入 removeBlock。最终新数组的所有元素均会作为参数被传入 doBlock。
*/
- (NSArray<ValueType> *)lookin_resizeWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, ValueType obj))removeBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock __attribute__((warn_unused_result));
+ (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block;
/**
检查 index 位置是否有元素存在
*/
- (BOOL)lookin_hasIndex:(NSInteger)index;
- (NSArray *)lookin_map:(id (^)(NSUInteger idx, ValueType value))block;
- (NSArray<ValueType> *)lookin_filter:(BOOL (^)( ValueType obj))block;
- (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block;
/// 返回最后一个 block 返回 YES 的元素
- (ValueType)lookin_lastFiltered:(BOOL (^)(ValueType obj))block;
- (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, ValueType obj))block;
- (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(CGFloat)initialAccumlator;
- (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger accumulator, NSUInteger idx, ValueType obj))block initialAccumlator:(NSInteger)initialAccumlator;
- (BOOL)lookin_all:(BOOL (^)(ValueType obj))block;
- (BOOL)lookin_any:(BOOL (^)(ValueType obj))block;
- (NSArray<ValueType> *)lookin_arrayByRemovingObject:(ValueType)obj;
- (NSArray<ValueType> *)lookin_nonredundantArray;
- (ValueType)lookin_safeObjectAtIndex:(NSInteger)idx;
/// 字符串长度从短到长,即 length 小的字符串的 idx 更小
- (NSArray<ValueType> *)lookin_sortedArrayByStringLength;
@end
@interface NSMutableArray<ValueType> (Lookin)
/**
如果当前数组长度比 count 小则会补充新元素(被补充的元素由 addBlock 返回),如果当前数组长度比 count 大则多余的元素会被作为参数传入 notDequeued。然后从 idx 为 0 算起,前 count 个元素会被作为参数传入 doBlock
*/
- (void)lookin_dequeueWithCount:(NSUInteger)count add:(ValueType (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, ValueType obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, ValueType obj))doBlock;
- (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, ValueType obj))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,300 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSArray+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/9/3.
// https://lookin.work
//
#import "NSArray+Lookin.h"
@implementation NSArray (Lookin)
- (NSArray *)lookin_resizeWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock remove:(void (^)(NSUInteger idx, id obj))removeBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock {
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
if (self.count > i) {
id obj = [self objectAtIndex:i];
[resultArray addObject:obj];
if (doBlock) {
doBlock(i, obj);
}
} else {
if (addBlock) {
id newObj = addBlock(i);
if (newObj) {
[resultArray addObject:newObj];
if (doBlock) {
doBlock(i, newObj);
}
} else {
NSAssert(NO, @"");
}
} else {
NSAssert(NO, @"");
}
}
}
if (removeBlock) {
if (self.count > count) {
for (NSUInteger i = count; i < self.count; i++) {
id obj = [self objectAtIndex:i];
removeBlock(i, obj);
}
}
}
return [resultArray copy];
}
+ (NSArray *)lookin_arrayWithCount:(NSUInteger)count block:(id (^)(NSUInteger idx))block {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for (NSUInteger i = 0; i < count; i++) {
id obj = block(i);
if (obj) {
[array addObject:obj];
}
}
return [array copy];
}
- (BOOL)lookin_hasIndex:(NSInteger)index {
if (index == NSNotFound || index < 0) {
return NO;
}
return self.count > index;
}
- (NSArray *)lookin_map:(id (^)(NSUInteger , id))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray *array = [[NSMutableArray alloc] initWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
id newObj = block(idx, obj);
if (newObj) {
[array addObject:newObj];
}
}];
return [array copy];
}
- (NSArray *)lookin_filter:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableArray *mArray = [NSMutableArray array];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
[mArray addObject:obj];
}
}];
return [mArray copy];
}
- (id)lookin_firstFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (id)lookin_lastFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (id)lookin_reduce:(id (^)(id accumulator, NSUInteger idx, id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id accumulator = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (CGFloat)lookin_reduceCGFloat:(CGFloat (^)(CGFloat accumulator, NSUInteger idx, id obj))block initialAccumlator:(CGFloat)initialAccumlator {
if (!block) {
NSAssert(NO, @"");
return initialAccumlator;
}
__block CGFloat accumulator = initialAccumlator;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (NSInteger)lookin_reduceInteger:(NSInteger (^)(NSInteger, NSUInteger, id))block initialAccumlator:(NSInteger)initialAccumlator {
if (!block) {
NSAssert(NO, @"");
return initialAccumlator;
}
__block NSInteger accumulator = initialAccumlator;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
accumulator = block(accumulator, idx, obj);
}];
return accumulator;
}
- (BOOL)lookin_all:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL allPass = YES;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(obj);
if (!boolValue) {
allPass = NO;
*stop = YES;
}
}];
return allPass;
}
- (BOOL)lookin_any:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL anyPass = NO;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(obj);
if (boolValue) {
anyPass = YES;
*stop = YES;
}
}];
return anyPass;
}
- (NSArray *)lookin_arrayByRemovingObject:(id)obj {
if (!obj || ![self containsObject:obj]) {
return self;
}
NSMutableArray *mutableArray = self.mutableCopy;
[mutableArray removeObject:obj];
return mutableArray.copy;
}
- (NSArray *)lookin_nonredundantArray {
NSSet *set = [NSSet setWithArray:self];
NSArray *newArray = [set allObjects];
return newArray;
}
- (id)lookin_safeObjectAtIndex:(NSInteger)idx {
if (idx == NSNotFound || idx < 0) {
return nil;
}
if (self.count <= idx) {
return nil;
}
return [self objectAtIndex:idx];
}
- (NSArray *)lookin_sortedArrayByStringLength {
NSArray<NSString *> *sortedArray = [self sortedArrayUsingComparator:^NSComparisonResult(NSString *obj1, NSString *obj2) {
if (obj1.length > obj2.length) {
return NSOrderedDescending;
} else if (obj1.length == obj2.length) {
return NSOrderedSame;
} else {
return NSOrderedAscending;
}
}];
return sortedArray;
}
@end
@implementation NSMutableArray (Lookin)
- (void)lookin_dequeueWithCount:(NSUInteger)count add:(id (^)(NSUInteger idx))addBlock notDequeued:(void (^)(NSUInteger idx, id obj))notDequeuedBlock doNext:(void (^)(NSUInteger idx, id obj))doBlock {
for (NSUInteger i = 0; i < count; i++) {
if ([self lookin_hasIndex:i]) {
id obj = [self objectAtIndex:i];
if (doBlock) {
doBlock(i, obj);
}
} else {
if (addBlock) {
id newObj = addBlock(i);
if (newObj) {
[self addObject:newObj];
if (doBlock) {
doBlock(i, newObj);
}
} else {
NSAssert(NO, @"");
}
} else {
NSAssert(NO, @"");
}
}
}
if (notDequeuedBlock) {
if (self.count > count) {
for (NSUInteger i = count; i < self.count; i++) {
id obj = [self objectAtIndex:i];
notDequeuedBlock(i, obj);
}
}
}
}
- (void)lookin_removeObjectsPassingTest:(BOOL (^)(NSUInteger idx, id obj))block {
if (!block) {
return;
}
NSMutableIndexSet *indexSet = [NSMutableIndexSet indexSet];
[self enumerateObjectsUsingBlock:^(id _Nonnull currentObj, NSUInteger idx, BOOL * _Nonnull stop) {
BOOL boolValue = block(idx, currentObj);
if (boolValue) {
[indexSet addIndex:idx];
}
}];
[self removeObjectsAtIndexes:indexSet];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,108 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+Lookin.h
// Lookin
//
// Created by Li Kai on 2018/12/22.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
#import "LookinCodingValueType.h"
@interface NSObject (Lookin)
#pragma mark - Data Bind
/**
给对象绑定上另一个对象以供后续取出使用,如果 object 传入 nil 则会清除该 key 之前绑定的对象
@attention 被绑定的对象会被 strong 强引用
@note 内部是使用 objc_setAssociatedObject / objc_getAssociatedObject 来实现
@code
- (UITableViewCell *)cellForIndexPath:(NSIndexPath *)indexPath {
// 1在这里给 button 绑定上 indexPath 对象
[cell lookin_bindObject:indexPath forKey:@"indexPath"];
}
- (void)didTapButton:(UIButton *)button {
// 2在这里取出被点击的 button 的 indexPath 对象
NSIndexPath *indexPathTapped = [button lookin_getBindObjectForKey:@"indexPath"];
}
@endcode
*/
- (void)lookin_bindObject:(id)object forKey:(NSString *)key;
/**
给对象绑定上另一个对象以供后续取出使用,但相比于 lookin_bindObject:forKey:,该方法不会 strong 强引用传入的 object
*/
- (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key;
/**
取出之前使用 bind 方法绑定的对象
*/
- (id)lookin_getBindObjectForKey:(NSString *)key;
/**
给对象绑定上一个 double 值以供后续取出使用
*/
- (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindDouble:forKey: 绑定的值
*/
- (double)lookin_getBindDoubleForKey:(NSString *)key;
/**
给对象绑定上一个 BOOL 值以供后续取出使用
*/
- (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindBOOL:forKey: 绑定的值
*/
- (BOOL)lookin_getBindBOOLForKey:(NSString *)key;
/**
给对象绑定上一个 long 值以供后续取出使用
*/
- (void)lookin_bindLong:(long)longValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindLong:forKey: 绑定的值
*/
- (long)lookin_getBindLongForKey:(NSString *)key;
/**
给对象绑定上一个 CGPoint 值以供后续取出使用
*/
- (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key;
/**
取出之前用 lookin_bindPoint:forKey: 绑定的值
*/
- (CGPoint)lookin_getBindPointForKey:(NSString *)key;
/**
移除之前使用 bind 方法绑定的对象
*/
- (void)lookin_clearBindForKey:(NSString *)key;
@end
@interface NSObject (Lookin_Coding)
/// 会把 NSImage/UIImage 转换为 NSData把 NSColor/UIColor 转换回 NSNumber 数组(rgba)
- (id)lookin_encodedObjectWithType:(LookinCodingValueType)type;
/// 会把 NSData 转换回 NSImage/UIImage把 NSNumber 数组(rgba) 转换为 NSColor/UIColor
- (id)lookin_decodedObjectWithType:(LookinCodingValueType)type;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,238 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSObject+Lookin.m
// Lookin
//
// Created by Li Kai on 2018/12/22.
// https://lookin.work
//
#import "NSObject+Lookin.h"
#import <objc/runtime.h>
#import "TargetConditionals.h"
#import "LookinWeakContainer.h"
@implementation NSObject (Lookin)
#pragma mark - Data Bind
static char kAssociatedObjectKey_LookinAllBindObjects;
- (NSMutableDictionary<id, id> *)lookin_allBindObjects {
NSMutableDictionary<id, id> *dict = objc_getAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects);
if (!dict) {
dict = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, &kAssociatedObjectKey_LookinAllBindObjects, dict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return dict;
}
- (void)lookin_bindObject:(id)object forKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return;
}
@synchronized (self) {
if (object) {
[[self lookin_allBindObjects] setObject:object forKey:key];
} else {
[[self lookin_allBindObjects] removeObjectForKey:key];
}
}
}
- (id)lookin_getBindObjectForKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return nil;
}
@synchronized (self) {
id storedObj = [[self lookin_allBindObjects] objectForKey:key];
if ([storedObj isKindOfClass:[LookinWeakContainer class]]) {
storedObj = [(LookinWeakContainer *)storedObj object];
}
return storedObj;
}
}
- (void)lookin_bindObjectWeakly:(id)object forKey:(NSString *)key {
if (!key.length) {
NSAssert(NO, @"");
return;
}
if (object) {
LookinWeakContainer *container = [[LookinWeakContainer alloc] init];
container.object = object;
[self lookin_bindObject:container forKey:key];
} else {
[self lookin_bindObject:nil forKey:key];
}
}
- (void)lookin_bindDouble:(double)doubleValue forKey:(NSString *)key {
[self lookin_bindObject:@(doubleValue) forKey:key];
}
- (double)lookin_getBindDoubleForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
double doubleValue = [(NSNumber *)object doubleValue];
return doubleValue;
} else {
return 0.0;
}
}
- (void)lookin_bindBOOL:(BOOL)boolValue forKey:(NSString *)key {
[self lookin_bindObject:@(boolValue) forKey:key];
}
- (BOOL)lookin_getBindBOOLForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
BOOL boolValue = [(NSNumber *)object boolValue];
return boolValue;
} else {
return NO;
}
}
- (void)lookin_bindLong:(long)longValue forKey:(NSString *)key {
[self lookin_bindObject:@(longValue) forKey:key];
}
- (long)lookin_getBindLongForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSNumber class]]) {
long longValue = [(NSNumber *)object longValue];
return longValue;
} else {
return 0;
}
}
- (void)lookin_bindPoint:(CGPoint)pointValue forKey:(NSString *)key {
#if TARGET_OS_IPHONE
[self lookin_bindObject:[NSValue valueWithCGPoint:pointValue] forKey:key];
#elif TARGET_OS_MAC
NSPoint nsPoint = NSMakePoint(pointValue.x, pointValue.y);
[self lookin_bindObject:[NSValue valueWithPoint:nsPoint] forKey:key];
#endif
}
- (CGPoint)lookin_getBindPointForKey:(NSString *)key {
id object = [self lookin_getBindObjectForKey:key];
if ([object isKindOfClass:[NSValue class]]) {
#if TARGET_OS_IPHONE
CGPoint pointValue = [(NSValue *)object CGPointValue];
#elif TARGET_OS_MAC
NSPoint nsPointValue = [(NSValue *)object pointValue];
CGPoint pointValue = CGPointMake(nsPointValue.x, nsPointValue.y);
#endif
return pointValue;
} else {
return CGPointZero;
}
}
- (void)lookin_clearBindForKey:(NSString *)key {
[self lookin_bindObject:nil forKey:key];
}
@end
@implementation NSObject (Lookin_Coding)
- (id)lookin_encodedObjectWithType:(LookinCodingValueType)type {
if (type == LookinCodingValueTypeColor) {
if ([self isKindOfClass:[LookinColor class]]) {
CGFloat r, g, b, a;
#if TARGET_OS_IPHONE
CGFloat white;
if ([(UIColor *)self getRed:&r green:&g blue:&b alpha:&a]) {
// valid
} else if ([(UIColor *)self getWhite:&white alpha:&a]) {
r = white;
g = white;
b = white;
} else {
NSAssert(NO, @"");
r = 0;
g = 0;
b = 0;
a = 0;
}
#elif TARGET_OS_MAC
NSColor *color = [((NSColor *)self) colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
[color getRed:&r green:&g blue:&b alpha:&a];
#endif
NSArray<NSNumber *> *rgba = @[@(r), @(g), @(b), @(a)];
return rgba;
} else {
NSAssert(NO, @"");
return nil;
}
} else if (type == LookinCodingValueTypeImage) {
#if TARGET_OS_IPHONE
if ([self isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)self;
return UIImagePNGRepresentation(image);
} else {
NSAssert(NO, @"");
return nil;
}
#elif TARGET_OS_MAC
if ([self isKindOfClass:[NSImage class]]) {
NSImage *image = (NSImage *)self;
return [image TIFFRepresentation];
} else {
NSAssert(NO, @"");
return nil;
}
#endif
} else {
return self;
}
}
- (id)lookin_decodedObjectWithType:(LookinCodingValueType)type {
if (type == LookinCodingValueTypeColor) {
if ([self isKindOfClass:[NSArray class]]) {
NSArray<NSNumber *> *rgba = (NSArray *)self;
CGFloat r = [rgba[0] doubleValue];
CGFloat g = [rgba[1] doubleValue];
CGFloat b = [rgba[2] doubleValue];
CGFloat a = [rgba[3] doubleValue];
LookinColor *color = [LookinColor colorWithRed:r green:g blue:b alpha:a];
return color;
} else {
NSAssert(NO, @"");
return nil;
}
} else if (type == LookinCodingValueTypeImage) {
if ([self isKindOfClass:[NSData class]]) {
LookinImage *image = [[LookinImage alloc] initWithData:(NSData *)self];
return image;
} else {
NSAssert(NO, @"");
return nil;
}
} else {
return self;
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,39 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSSet+Lookin.h
// Lookin
//
// Created by Li Kai on 2019/1/13.
// https://lookin.work
//
#import "LookinDefines.h"
#import "TargetConditionals.h"
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#elif TARGET_OS_MAC
#import <Appkit/Appkit.h>
#endif
@interface NSSet<__covariant ValueType> (Lookin)
- (NSSet *)lookin_map:(id (^)(ValueType obj))block;
- (ValueType)lookin_firstFiltered:(BOOL (^)(ValueType obj))block;
- (NSSet<ValueType> *)lookin_filter:(BOOL (^)(ValueType obj))block;
/**
是否有任何一个元素满足某条件
@note 元素将被依次传入 block 里,如果任何一个 block 返回 YES则该方法返回 YES。如果所有 block 均返回 NO则该方法返回 NO。
*/
- (BOOL)lookin_any:(BOOL (^)(ValueType obj))block;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,81 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSSet+Lookin.m
// Lookin
//
// Created by Li Kai on 2019/1/13.
// https://lookin.work
//
#import "NSSet+Lookin.h"
@implementation NSSet (Lookin)
- (NSSet *)lookin_map:(id (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableSet *newSet = [NSMutableSet setWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
id newObj = block(obj);
if (newObj) {
[newSet addObject:newObj];
}
}];
return [newSet copy];
}
- (id)lookin_firstFiltered:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
__block id targetObj = nil;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
targetObj = obj;
*stop = YES;
}
}];
return targetObj;
}
- (NSSet *)lookin_filter:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return nil;
}
NSMutableSet *mSet = [NSMutableSet set];
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
[mSet addObject:obj];
}
}];
return [mSet copy];
}
- (BOOL)lookin_any:(BOOL (^)(id obj))block {
if (!block) {
NSAssert(NO, @"");
return NO;
}
__block BOOL boolValue = NO;
[self enumerateObjectsUsingBlock:^(id _Nonnull obj, BOOL * _Nonnull stop) {
if (block(obj)) {
boolValue = YES;
*stop = YES;
}
}];
return boolValue;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,42 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSString+Lookin.h
// Lookin
//
// Created by Li Kai on 2019/5/11.
// https://lookin.work
//
#import "LookinDefines.h"
#import <Foundation/Foundation.h>
@interface NSString (Lookin)
/**
把 CGFloat 转成字符串,最多保留 3 位小数,转换后末尾的 0 会被删除
1.2341 => @"1.234", 2.1002 => @"2.1", 3.000 => @"3"
*/
+ (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal;
+ (NSString *)lookin_stringFromRect:(CGRect)rect;
+ (NSString *)lookin_stringFromInset:(LookinInsets)insets;
+ (NSString *)lookin_stringFromSize:(CGSize)size;
+ (NSString *)lookin_stringFromPoint:(CGPoint)point;
+ (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color;
- (NSString *)lookin_safeInitWithUTF8String:(const char *)string;
/// 把 1.2.3 这种 String 版本号转换成数字,可用于大小比较,如 110205 代表 11.2.5 版本
- (NSInteger)lookin_numbericOSVersion;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,117 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// NSString+Lookin.m
// Lookin
//
// Created by Li Kai on 2019/5/11.
// https://lookin.work
//
#import "NSString+Lookin.h"
@implementation NSString (Lookin)
+ (NSString *)lookin_stringFromDouble:(double)doubleValue decimal:(NSUInteger)decimal {
NSString *formatString = [NSString stringWithFormat:@"%%.%@f", @(decimal)];
NSString *string = [NSString stringWithFormat:formatString, doubleValue];
for (int i = 0; i < decimal; i++) {
if ([[string substringFromIndex:string.length - 1] isEqualToString:@"0"]) {
string = [string substringToIndex:string.length - 1];
}
}
if ([[string substringFromIndex:string.length - 1] isEqualToString:@"."]) {
string = [string substringToIndex:string.length - 1];
}
return string;
}
+ (NSString *)lookin_stringFromInset:(LookinInsets)insets {
return [NSString stringWithFormat:@"{%@, %@, %@, %@}",
[NSString lookin_stringFromDouble:insets.top decimal:2],
[NSString lookin_stringFromDouble:insets.left decimal:2],
[NSString lookin_stringFromDouble:insets.bottom decimal:2],
[NSString lookin_stringFromDouble:insets.right decimal:2]];
}
+ (NSString *)lookin_stringFromSize:(CGSize)size {
return [NSString stringWithFormat:@"{%@, %@}",
[NSString lookin_stringFromDouble:size.width decimal:2],
[NSString lookin_stringFromDouble:size.height decimal:2]];
}
+ (NSString *)lookin_stringFromPoint:(CGPoint)point {
return [NSString stringWithFormat:@"{%@, %@}",
[NSString lookin_stringFromDouble:point.x decimal:2],
[NSString lookin_stringFromDouble:point.y decimal:2]];
}
+ (NSString *)lookin_stringFromRect:(CGRect)rect {
return [NSString stringWithFormat:@"{%@, %@, %@, %@}",
[NSString lookin_stringFromDouble:rect.origin.x decimal:2],
[NSString lookin_stringFromDouble:rect.origin.y decimal:2],
[NSString lookin_stringFromDouble:rect.size.width decimal:2],
[NSString lookin_stringFromDouble:rect.size.height decimal:2]];
}
+ (NSString *)lookin_rgbaStringFromColor:(LookinColor *)color {
if (!color) {
return @"nil";
}
#if TARGET_OS_IPHONE
UIColor *rgbColor = color;
#elif TARGET_OS_MAC
NSColor *rgbColor = [color colorUsingColorSpace:NSColorSpace.sRGBColorSpace];
#endif
CGFloat r, g, b, a;
[rgbColor getRed:&r green:&g blue:&b alpha:&a];
NSString *colorDesc;
if (a >= 1) {
colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f)", r * 255, g * 255, b * 255];
} else {
colorDesc = [NSString stringWithFormat:@"(%.0f, %.0f, %.0f, %@)", r * 255, g * 255, b * 255, [NSString lookin_stringFromDouble:a decimal:2]];
}
return colorDesc;
}
- (NSString *)lookin_safeInitWithUTF8String:(const char *)string {
if (NULL != string) {
return [self initWithUTF8String:string];
}
return nil;
}
- (NSInteger)lookin_numbericOSVersion {
if (self.length == 0) {
NSAssert(NO, @"");
return 0;
}
NSArray *versionArr = [self componentsSeparatedByString:@"."];
if (versionArr.count != 3) {
NSAssert(NO, @"");
return 0;
}
NSInteger numbericOSVersion = 0;
NSInteger pos = 0;
while ([versionArr count] > pos && pos < 3) {
numbericOSVersion += ([[versionArr objectAtIndex:pos] integerValue] * pow(10, (4 - pos * 2)));
pos++;
}
return numbericOSVersion;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,72 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAppInfo.h
// qmuidemo
//
// Created by Li Kai on 2018/11/3.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinDefines.h"
typedef NS_ENUM(NSInteger, LookinAppInfoDevice) {
LookinAppInfoDeviceSimulator, // 模拟器
LookinAppInfoDeviceIPad, // iPad 真机
LookinAppInfoDeviceOthers // 应该视为 iPhone 真机
};
@interface LookinAppInfo : NSObject <NSSecureCoding, NSCopying>
/// 每次启动 app 时都会随机生成一个 appInfoIdentifier 直到 app 被 kill 掉
@property(nonatomic, assign) NSUInteger appInfoIdentifier;
/// mac 端应该先读取该属性,如果为 YES 则表示应该使用之前保存的旧 appInfo 对象即可
@property(nonatomic, assign) BOOL shouldUseCache;
/// LookinServer 的版本
@property(nonatomic, assign) int serverVersion;
/// 类似 "1.1.9",只在 1.2.3 以及之后的 LookinServer 版本里有值
@property(nonatomic, assign) NSString *serverReadableVersion;
/// 如果 iOS 侧使用了 SPM 或引入了 Swift Subspec则该属性为 1
/// 如果 iOS 侧没使用,则该属性为 -1
/// 如果不知道,则该属性为 0
@property(nonatomic, assign) int swiftEnabledInLookinServer;
/// app 的当前截图
@property(nonatomic, strong) LookinImage *screenshot;
/// 可能为 nil比如新建的 iOS 空项目
@property(nonatomic, strong) LookinImage *appIcon;
/// @"微信读书"
@property(nonatomic, copy) NSString *appName;
/// hughkli.lookin
@property(nonatomic, copy) NSString *appBundleIdentifier;
/// @"iPhone X"
@property(nonatomic, copy) NSString *deviceDescription;
/// @"12.1"
@property(nonatomic, copy) NSString *osDescription;
/// 返回 os 的主版本号,比如 iOS 12.1 的设备将返回 12iOS 13.2.1 的设备将返回 13
@property(nonatomic, assign) NSUInteger osMainVersion;
/// 设备类型
@property(nonatomic, assign) LookinAppInfoDevice deviceType;
/// 屏幕的宽度
@property(nonatomic, assign) double screenWidth;
/// 屏幕的高度
@property(nonatomic, assign) double screenHeight;
/// 是几倍的屏幕
@property(nonatomic, assign) double screenScale;
- (BOOL)isEqualToAppInfo:(LookinAppInfo *)info;
#if TARGET_OS_IPHONE
+ (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray<NSNumber *> *)localIdentifiers;
#else
@property(nonatomic, assign) NSTimeInterval cachedTimestamp;
#endif
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,242 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAppInfo.m
// qmuidemo
//
// Created by Li Kai on 2018/11/3.
// Copyright © 2018 QMUI Team. All rights reserved.
//
#import "LookinAppInfo.h"
#import "LKS_MultiplatformAdapter.h"
static NSString * const CodingKey_AppIcon = @"1";
static NSString * const CodingKey_Screenshot = @"2";
static NSString * const CodingKey_DeviceDescription = @"3";
static NSString * const CodingKey_OsDescription = @"4";
static NSString * const CodingKey_AppName = @"5";
static NSString * const CodingKey_ScreenWidth = @"6";
static NSString * const CodingKey_ScreenHeight = @"7";
static NSString * const CodingKey_DeviceType = @"8";
@implementation LookinAppInfo
- (id)copyWithZone:(NSZone *)zone {
LookinAppInfo *newAppInfo = [[LookinAppInfo allocWithZone:zone] init];
newAppInfo.appIcon = self.appIcon;
newAppInfo.appName = self.appName;
newAppInfo.deviceDescription = self.deviceDescription;
newAppInfo.osDescription = self.osDescription;
newAppInfo.osMainVersion = self.osMainVersion;
newAppInfo.deviceType = self.deviceType;
newAppInfo.screenWidth = self.screenWidth;
newAppInfo.screenHeight = self.screenHeight;
newAppInfo.screenScale = self.screenScale;
newAppInfo.appInfoIdentifier = self.appInfoIdentifier;
return newAppInfo;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
self.serverVersion = [aDecoder decodeIntForKey:@"serverVersion"];
self.serverReadableVersion = [aDecoder decodeObjectForKey:@"serverReadableVersion"];
self.swiftEnabledInLookinServer = [aDecoder decodeIntForKey:@"swiftEnabledInLookinServer"];
NSData *screenshotData = [aDecoder decodeObjectForKey:CodingKey_Screenshot];
self.screenshot = [[LookinImage alloc] initWithData:screenshotData];
NSData *appIconData = [aDecoder decodeObjectForKey:CodingKey_AppIcon];
self.appIcon = [[LookinImage alloc] initWithData:appIconData];
self.appName = [aDecoder decodeObjectForKey:CodingKey_AppName];
self.appBundleIdentifier = [aDecoder decodeObjectForKey:@"appBundleIdentifier"];
self.deviceDescription = [aDecoder decodeObjectForKey:CodingKey_DeviceDescription];
self.osDescription = [aDecoder decodeObjectForKey:CodingKey_OsDescription];
self.osMainVersion = [aDecoder decodeIntegerForKey:@"osMainVersion"];
self.deviceType = [aDecoder decodeIntegerForKey:CodingKey_DeviceType];
self.screenWidth = [aDecoder decodeDoubleForKey:CodingKey_ScreenWidth];
self.screenHeight = [aDecoder decodeDoubleForKey:CodingKey_ScreenHeight];
self.screenScale = [aDecoder decodeDoubleForKey:@"screenScale"];
self.appInfoIdentifier = [aDecoder decodeIntegerForKey:@"appInfoIdentifier"];
self.shouldUseCache = [aDecoder decodeBoolForKey:@"shouldUseCache"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:self.serverVersion forKey:@"serverVersion"];
[aCoder encodeObject:self.serverReadableVersion forKey:@"serverReadableVersion"];
[aCoder encodeInt:self.swiftEnabledInLookinServer forKey:@"swiftEnabledInLookinServer"];
#if TARGET_OS_IPHONE
NSData *screenshotData = UIImagePNGRepresentation(self.screenshot);
[aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot];
NSData *appIconData = UIImagePNGRepresentation(self.appIcon);
[aCoder encodeObject:appIconData forKey:CodingKey_AppIcon];
#elif TARGET_OS_MAC
NSData *screenshotData = [self.screenshot TIFFRepresentation];
[aCoder encodeObject:screenshotData forKey:CodingKey_Screenshot];
NSData *appIconData = [self.appIcon TIFFRepresentation];
[aCoder encodeObject:appIconData forKey:CodingKey_AppIcon];
#endif
[aCoder encodeObject:self.appName forKey:CodingKey_AppName];
[aCoder encodeObject:self.appBundleIdentifier forKey:@"appBundleIdentifier"];
[aCoder encodeObject:self.deviceDescription forKey:CodingKey_DeviceDescription];
[aCoder encodeObject:self.osDescription forKey:CodingKey_OsDescription];
[aCoder encodeInteger:self.osMainVersion forKey:@"osMainVersion"];
[aCoder encodeInteger:self.deviceType forKey:CodingKey_DeviceType];
[aCoder encodeDouble:self.screenWidth forKey:CodingKey_ScreenWidth];
[aCoder encodeDouble:self.screenHeight forKey:CodingKey_ScreenHeight];
[aCoder encodeDouble:self.screenScale forKey:@"screenScale"];
[aCoder encodeInteger:self.appInfoIdentifier forKey:@"appInfoIdentifier"];
[aCoder encodeBool:self.shouldUseCache forKey:@"shouldUseCache"];
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[LookinAppInfo class]]) {
return NO;
}
if ([self isEqualToAppInfo:object]) {
return YES;
}
return NO;
}
- (NSUInteger)hash {
return self.appName.hash ^ self.deviceDescription.hash ^ self.osDescription.hash ^ self.deviceType;
}
- (BOOL)isEqualToAppInfo:(LookinAppInfo *)info {
if (!info) {
return NO;
}
if ([self.appName isEqualToString:info.appName] && [self.deviceDescription isEqualToString:info.deviceDescription] && [self.osDescription isEqualToString:info.osDescription] && self.deviceType == info.deviceType) {
return YES;
}
return NO;
}
#if TARGET_OS_IPHONE
+ (LookinAppInfo *)currentInfoWithScreenshot:(BOOL)hasScreenshot icon:(BOOL)hasIcon localIdentifiers:(NSArray<NSNumber *> *)localIdentifiers {
NSInteger selfIdentifier = [self getAppInfoIdentifier];
if ([localIdentifiers containsObject:@(selfIdentifier)]) {
LookinAppInfo *info = [LookinAppInfo new];
info.appInfoIdentifier = selfIdentifier;
info.shouldUseCache = YES;
return info;
}
LookinAppInfo *info = [[LookinAppInfo alloc] init];
info.serverReadableVersion = LOOKIN_SERVER_READABLE_VERSION;
#ifdef LOOKIN_SERVER_SWIFT_ENABLED
info.swiftEnabledInLookinServer = 1;
#else
info.swiftEnabledInLookinServer = -1;
#endif
info.appInfoIdentifier = selfIdentifier;
info.appName = [self appName];
info.deviceDescription = [UIDevice currentDevice].name;
info.appBundleIdentifier = [[NSBundle mainBundle] bundleIdentifier];
if ([self isSimulator]) {
info.deviceType = LookinAppInfoDeviceSimulator;
} else if ([LKS_MultiplatformAdapter isiPad]) {
info.deviceType = LookinAppInfoDeviceIPad;
} else {
info.deviceType = LookinAppInfoDeviceOthers;
}
info.osDescription = [UIDevice currentDevice].systemVersion;
NSString *mainVersionStr = [[[UIDevice currentDevice] systemVersion] componentsSeparatedByString:@"."].firstObject;
info.osMainVersion = [mainVersionStr integerValue];
CGSize screenSize = [LKS_MultiplatformAdapter mainScreenBounds].size;
info.screenWidth = screenSize.width;
info.screenHeight = screenSize.height;
info.screenScale = [LKS_MultiplatformAdapter mainScreenScale];
if (hasScreenshot) {
info.screenshot = [self screenshotImage];
}
if (hasIcon) {
info.appIcon = [self appIcon];
}
return info;
}
+ (NSString *)appName {
NSDictionary *info = [[NSBundle mainBundle] infoDictionary];
NSString *displayName = [info objectForKey:@"CFBundleDisplayName"];
NSString *name = [info objectForKey:@"CFBundleName"];
return displayName.length ? displayName : name;
}
+ (UIImage *)appIcon {
#if TARGET_OS_TV
return nil;
#else
NSString *imageName = [[[[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIcons"] objectForKey:@"CFBundlePrimaryIcon"] objectForKey:@"CFBundleIconFiles"] lastObject];
if (!imageName.length) {
// name AppIcon60x60 nil return [UIImage imageNamed:nil] console "CUICatalog: Invalid asset name supplied: '(null)'"
return nil;
}
return [UIImage imageNamed:imageName];
#endif
}
+ (UIImage *)screenshotImage {
UIWindow *window = [LKS_MultiplatformAdapter keyWindow];
if (!window) {
return nil;
}
CGSize size = window.bounds.size;
if (size.width <= 0 || size.height <= 0) {
// *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UIGraphicsBeginImageContext() failed to allocate CGBitampContext: size={0, 0}, scale=3.000000, bitmapInfo=0x2002. Use UIGraphicsImageRenderer to avoid this assert.'
// https://github.com/hughkli/Lookin/issues/21
return nil;
}
UIGraphicsBeginImageContextWithOptions(size, YES, 0.4);
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
+ (BOOL)isSimulator {
if (TARGET_OS_SIMULATOR) {
return YES;
}
return NO;
}
#endif
+ (NSInteger)getAppInfoIdentifier {
static dispatch_once_t onceToken;
static NSInteger identifier = 0;
dispatch_once(&onceToken,^{
identifier = [[NSDate date] timeIntervalSince1970];
});
return identifier;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,257 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttrIdentifiers.h
// Lookin
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import <Foundation/Foundation.h>
#pragma mark - Group
typedef NSString * LookinAttrGroupIdentifier;
extern LookinAttrGroupIdentifier const LookinAttrGroup_None;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Class;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Relation;
extern LookinAttrGroupIdentifier const LookinAttrGroup_Layout;
extern LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout;
extern LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UILabel;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIControl;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIButton;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITableView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UITextField;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UIStackView;
extern LookinAttrGroupIdentifier const LookinAttrGroup_UserCustom;
#pragma mark - Section
typedef NSString * LookinAttrSectionIdentifier;
extern LookinAttrSectionIdentifier const LookinAttrSec_None;
extern LookinAttrSectionIdentifier const LookinAttrSec_UserCustom;
extern LookinAttrSectionIdentifier const LookinAttrSec_Class_Class;
extern LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position;
extern LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints;
extern LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont;
extern LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Axis;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Distribution;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Alignment;
extern LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Spacing;
#pragma mark - Attr
typedef NSString * LookinAttrIdentifier;
extern LookinAttrIdentifier const LookinAttr_None;
/// 用户自定义的
extern LookinAttrIdentifier const LookinAttr_UserCustom;
extern LookinAttrIdentifier const LookinAttr_Class_Class_Class;
extern LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation;
extern LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame;
extern LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds;
extern LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea;
extern LookinAttrIdentifier const LookinAttr_Layout_Position_Position;
extern LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints;
extern LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode;
extern LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag;
extern LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name;
extern LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open;
extern LookinAttrIdentifier const LookinAttr_UILabel_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UILabel_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines;
extern LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode;
extern LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont;
extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled;
extern LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected;
extern LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge;
extern LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce;
extern LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITableView_Style_Style;
extern LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number;
extern LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style;
extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UITextView_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable;
extern LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable;
extern LookinAttrIdentifier const LookinAttr_UITextView_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset;
extern LookinAttrIdentifier const LookinAttr_UITextField_Text_Text;
extern LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder;
extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Name;
extern LookinAttrIdentifier const LookinAttr_UITextField_Font_Size;
extern LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color;
extern LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing;
extern LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion;
extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont;
extern LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize;
extern LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode;
extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style;
extern LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Axis_Axis;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Distribution_Distribution;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Alignment_Alignment;
extern LookinAttrIdentifier const LookinAttr_UIStackView_Spacing_Spacing;
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,253 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// LookinAttrIdentifiers.m
// Lookin
//
// Created by Li Kai on 2019/9/18.
// https://lookin.work
//
#import "LookinAttrIdentifiers.h"
// value AppDelegate runTests test
// value preference value userDefaults
#pragma mark - Group
LookinAttrGroupIdentifier const LookinAttrGroup_None = @"n";
LookinAttrGroupIdentifier const LookinAttrGroup_Class = @"c";
LookinAttrGroupIdentifier const LookinAttrGroup_Relation = @"r";
LookinAttrGroupIdentifier const LookinAttrGroup_Layout = @"l";
LookinAttrGroupIdentifier const LookinAttrGroup_AutoLayout = @"a";
LookinAttrGroupIdentifier const LookinAttrGroup_ViewLayer = @"vl";
LookinAttrGroupIdentifier const LookinAttrGroup_UIImageView = @"i";
LookinAttrGroupIdentifier const LookinAttrGroup_UILabel = @"la";
LookinAttrGroupIdentifier const LookinAttrGroup_UIControl = @"co";
LookinAttrGroupIdentifier const LookinAttrGroup_UIButton = @"b";
LookinAttrGroupIdentifier const LookinAttrGroup_UIScrollView = @"s";
LookinAttrGroupIdentifier const LookinAttrGroup_UITableView = @"ta";
LookinAttrGroupIdentifier const LookinAttrGroup_UITextView = @"te";
LookinAttrGroupIdentifier const LookinAttrGroup_UITextField = @"tf";
LookinAttrGroupIdentifier const LookinAttrGroup_UIVisualEffectView = @"ve";
LookinAttrGroupIdentifier const LookinAttrGroup_UIStackView = @"UIStackView";
LookinAttrGroupIdentifier const LookinAttrGroup_UserCustom = @"guc"; // user custom
#pragma mark - Section
LookinAttrSectionIdentifier const LookinAttrSec_None = @"n";
LookinAttrSectionIdentifier const LookinAttrSec_UserCustom = @"sec_ctm";
LookinAttrSectionIdentifier const LookinAttrSec_Class_Class = @"cl_c";
LookinAttrSectionIdentifier const LookinAttrSec_Relation_Relation = @"r_r";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Frame = @"l_f";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Bounds = @"l_b";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_SafeArea = @"l_s";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_Position = @"l_p";
LookinAttrSectionIdentifier const LookinAttrSec_Layout_AnchorPoint = @"l_a";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Hugging = @"a_h";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Resistance = @"a_r";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_Constraints = @"a_c";
LookinAttrSectionIdentifier const LookinAttrSec_AutoLayout_IntrinsicSize = @"a_i";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Visibility = @"v_v";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_InterationAndMasks = @"v_i";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Corner = @"v_c";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_BgColor = @"v_b";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Border = @"v_bo";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Shadow = @"v_s";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_ContentMode = @"v_co";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_TintColor = @"v_t";
LookinAttrSectionIdentifier const LookinAttrSec_ViewLayer_Tag = @"v_ta";
LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Name = @"i_n";
LookinAttrSectionIdentifier const LookinAttrSec_UIImageView_Open = @"i_o";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Text = @"lb_t";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Font = @"lb_f";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_NumberOfLines = @"lb_n";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_TextColor = @"lb_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_BreakMode = @"lb_b";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_Alignment = @"lb_a";
LookinAttrSectionIdentifier const LookinAttrSec_UILabel_CanAdjustFont = @"lb_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_EnabledSelected = @"c_e";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_VerAlignment = @"c_v";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_HorAlignment = @"c_h";
LookinAttrSectionIdentifier const LookinAttrSec_UIControl_QMUIOutsideEdge = @"c_o";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ContentInsets = @"b_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_TitleInsets = @"b_t";
LookinAttrSectionIdentifier const LookinAttrSec_UIButton_ImageInsets = @"b_i";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentInset = @"s_c";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_AdjustedInset = @"s_a";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_IndicatorInset = @"s_i";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Offset = @"s_o";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentSize = @"s_cs";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Behavior = @"s_b";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ShowsIndicator = @"s_si";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Bounce = @"s_bo";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ScrollPaging = @"s_s";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_ContentTouches = @"s_ct";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_Zoom = @"s_z";
LookinAttrSectionIdentifier const LookinAttrSec_UIScrollView_QMUIInitialInset = @"s_ii";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_Style = @"t_s";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SectionsNumber = @"t_sn";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_RowsNumber = @"t_r";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorStyle = @"t_ss";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorColor = @"t_sc";
LookinAttrSectionIdentifier const LookinAttrSec_UITableView_SeparatorInset = @"t_si";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Basic = @"tv_b";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Text = @"tv_t";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Font = @"tv_f";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_TextColor = @"tv_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_Alignment = @"tv_a";
LookinAttrSectionIdentifier const LookinAttrSec_UITextView_ContainerInset = @"tv_c";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Text = @"tf_t";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Placeholder = @"tf_p";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Font = @"tf_f";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_TextColor = @"tf_tc";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Alignment = @"tf_a";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_Clears = @"tf_c";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_CanAdjustFont = @"tf_ca";
LookinAttrSectionIdentifier const LookinAttrSec_UITextField_ClearButtonMode = @"tf_cb";
LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_Style = @"ve_s";
LookinAttrSectionIdentifier const LookinAttrSec_UIVisualEffectView_QMUIForegroundColor = @"ve_f";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Axis = @"usv_axis";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Distribution = @"usv_dis";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Alignment = @"usv_align";
LookinAttrSectionIdentifier const LookinAttrSec_UIStackView_Spacing = @"usv_spa";
#pragma mark - Attr
LookinAttrIdentifier const LookinAttr_None = @"n";
LookinAttrIdentifier const LookinAttr_UserCustom = @"ctm";
LookinAttrIdentifier const LookinAttr_Class_Class_Class = @"c_c_c";
LookinAttrIdentifier const LookinAttr_Relation_Relation_Relation = @"r_r_r";
LookinAttrIdentifier const LookinAttr_Layout_Frame_Frame = @"l_f_f";
LookinAttrIdentifier const LookinAttr_Layout_Bounds_Bounds = @"l_b_b";
LookinAttrIdentifier const LookinAttr_Layout_SafeArea_SafeArea = @"l_s_s";
LookinAttrIdentifier const LookinAttr_Layout_Position_Position = @"l_p_p";
LookinAttrIdentifier const LookinAttr_Layout_AnchorPoint_AnchorPoint = @"l_a_a";
LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Hor = @"al_h_h";
LookinAttrIdentifier const LookinAttr_AutoLayout_Hugging_Ver = @"al_h_v";
LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Hor = @"al_r_h";
LookinAttrIdentifier const LookinAttr_AutoLayout_Resistance_Ver = @"al_r_v";
LookinAttrIdentifier const LookinAttr_AutoLayout_Constraints_Constraints = @"al_c_c";
LookinAttrIdentifier const LookinAttr_AutoLayout_IntrinsicSize_Size = @"cl_i_s";
LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Hidden = @"vl_v_h";
LookinAttrIdentifier const LookinAttr_ViewLayer_Visibility_Opacity = @"vl_v_o";
LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_Interaction = @"vl_i_i";
LookinAttrIdentifier const LookinAttr_ViewLayer_InterationAndMasks_MasksToBounds = @"vl_i_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_Corner_Radius = @"vl_c_r";
LookinAttrIdentifier const LookinAttr_ViewLayer_BgColor_BgColor = @"vl_b_b";
LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Color = @"vl_b_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_Border_Width = @"vl_b_w";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Color = @"vl_s_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Opacity = @"vl_s_o";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_Radius = @"vl_s_r";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetW = @"vl_s_ow";
LookinAttrIdentifier const LookinAttr_ViewLayer_Shadow_OffsetH = @"vl_s_oh";
LookinAttrIdentifier const LookinAttr_ViewLayer_ContentMode_Mode = @"vl_c_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Color = @"vl_t_c";
LookinAttrIdentifier const LookinAttr_ViewLayer_TintColor_Mode = @"vl_t_m";
LookinAttrIdentifier const LookinAttr_ViewLayer_Tag_Tag = @"vl_t_t";
LookinAttrIdentifier const LookinAttr_UIImageView_Name_Name = @"iv_n_n";
LookinAttrIdentifier const LookinAttr_UIImageView_Open_Open = @"iv_o_o";
LookinAttrIdentifier const LookinAttr_UILabel_Text_Text = @"lb_t_t";
LookinAttrIdentifier const LookinAttr_UILabel_Font_Name = @"lb_f_n";
LookinAttrIdentifier const LookinAttr_UILabel_Font_Size = @"lb_f_s";
LookinAttrIdentifier const LookinAttr_UILabel_NumberOfLines_NumberOfLines = @"lb_n_n";
LookinAttrIdentifier const LookinAttr_UILabel_TextColor_Color = @"lb_t_c";
LookinAttrIdentifier const LookinAttr_UILabel_Alignment_Alignment = @"lb_a_a";
LookinAttrIdentifier const LookinAttr_UILabel_BreakMode_Mode = @"lb_b_m";
LookinAttrIdentifier const LookinAttr_UILabel_CanAdjustFont_CanAdjustFont = @"lb_c_c";
LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Enabled = @"ct_e_e";
LookinAttrIdentifier const LookinAttr_UIControl_EnabledSelected_Selected = @"ct_e_s";
LookinAttrIdentifier const LookinAttr_UIControl_VerAlignment_Alignment = @"ct_v_a";
LookinAttrIdentifier const LookinAttr_UIControl_HorAlignment_Alignment = @"ct_h_a";
LookinAttrIdentifier const LookinAttr_UIControl_QMUIOutsideEdge_Edge = @"ct_o_e";
LookinAttrIdentifier const LookinAttr_UIButton_ContentInsets_Insets = @"bt_c_i";
LookinAttrIdentifier const LookinAttr_UIButton_TitleInsets_Insets = @"bt_t_i";
LookinAttrIdentifier const LookinAttr_UIButton_ImageInsets_Insets = @"bt_i_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_Offset_Offset = @"sv_o_o";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentSize_Size = @"sv_c_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentInset_Inset = @"sv_c_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_AdjustedInset_Inset = @"sv_a_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_Behavior_Behavior = @"sv_b_b";
LookinAttrIdentifier const LookinAttr_UIScrollView_IndicatorInset_Inset = @"sv_i_i";
LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_ScrollEnabled = @"sv_s_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_ScrollPaging_PagingEnabled = @"sv_s_p";
LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Ver = @"sv_b_v";
LookinAttrIdentifier const LookinAttr_UIScrollView_Bounce_Hor = @"sv_b_h";
LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Hor = @"sv_h_h";
LookinAttrIdentifier const LookinAttr_UIScrollView_ShowsIndicator_Ver = @"sv_s_v";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_Delay = @"sv_c_d";
LookinAttrIdentifier const LookinAttr_UIScrollView_ContentTouches_CanCancel = @"sv_c_c";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MinScale = @"sv_z_mi";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_MaxScale = @"sv_z_ma";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Scale = @"sv_z_s";
LookinAttrIdentifier const LookinAttr_UIScrollView_Zoom_Bounce = @"sv_z_b";
LookinAttrIdentifier const LookinAttr_UIScrollView_QMUIInitialInset_Inset = @"sv_qi_i";
LookinAttrIdentifier const LookinAttr_UITableView_Style_Style = @"tv_s_s";
LookinAttrIdentifier const LookinAttr_UITableView_SectionsNumber_Number = @"tv_s_n";
LookinAttrIdentifier const LookinAttr_UITableView_RowsNumber_Number = @"tv_r_n";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorInset_Inset = @"tv_s_i";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorColor_Color = @"tv_s_c";
LookinAttrIdentifier const LookinAttr_UITableView_SeparatorStyle_Style = @"tv_ss_s";
LookinAttrIdentifier const LookinAttr_UITextView_Font_Name = @"te_f_n";
LookinAttrIdentifier const LookinAttr_UITextView_Font_Size = @"te_f_s";
LookinAttrIdentifier const LookinAttr_UITextView_Basic_Editable = @"te_b_e";
LookinAttrIdentifier const LookinAttr_UITextView_Basic_Selectable = @"te_b_s";
LookinAttrIdentifier const LookinAttr_UITextView_Text_Text = @"te_t_t";
LookinAttrIdentifier const LookinAttr_UITextView_TextColor_Color = @"te_t_c";
LookinAttrIdentifier const LookinAttr_UITextView_Alignment_Alignment = @"te_a_a";
LookinAttrIdentifier const LookinAttr_UITextView_ContainerInset_Inset = @"te_c_i";
LookinAttrIdentifier const LookinAttr_UITextField_Text_Text = @"tf_t_t";
LookinAttrIdentifier const LookinAttr_UITextField_Placeholder_Placeholder = @"tf_p_p";
LookinAttrIdentifier const LookinAttr_UITextField_Font_Name = @"tf_f_n";
LookinAttrIdentifier const LookinAttr_UITextField_Font_Size = @"tf_f_s";
LookinAttrIdentifier const LookinAttr_UITextField_TextColor_Color = @"tf_t_c";
LookinAttrIdentifier const LookinAttr_UITextField_Alignment_Alignment = @"tf_a_a";
LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnBeginEditing = @"tf_c_c";
LookinAttrIdentifier const LookinAttr_UITextField_Clears_ClearsOnInsertion = @"tf_c_co";
LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_CanAdjustFont = @"tf_c_ca";
LookinAttrIdentifier const LookinAttr_UITextField_CanAdjustFont_MinSize = @"tf_c_m";
LookinAttrIdentifier const LookinAttr_UITextField_ClearButtonMode_Mode = @"tf_cb_m";
LookinAttrIdentifier const LookinAttr_UIVisualEffectView_Style_Style = @"ve_s_s";
LookinAttrIdentifier const LookinAttr_UIVisualEffectView_QMUIForegroundColor_Color = @"ve_f_c";
LookinAttrIdentifier const LookinAttr_UIStackView_Axis_Axis = @"usv_axis_axis";
LookinAttrIdentifier const LookinAttr_UIStackView_Distribution_Distribution = @"usv_dis_dis";
LookinAttrIdentifier const LookinAttr_UIStackView_Alignment_Alignment = @"usv_ali_ali";
LookinAttrIdentifier const LookinAttr_UIStackView_Spacing_Spacing = @"usv_spa_spa";
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

Some files were not shown because too many files have changed in this diff Show More