iOS | Build a Log Explore View

2023-07-20  本文已影响0人  清無
@interface DemoLogsView : UIWindow

+ (instancetype)shared;
- (void)setup;
- (void)appendLog:(NSString *)format, ...;
- (void)clearLogs;

@end

@interface DemoLogsView()<UITableViewDelegate, UITableViewDataSource>

@property (strong, nonatomic) UITableView *logsTb;
@property (strong, nonatomic) UIButton *toggleBtn;
@property (strong, nonatomic) UIButton *exportBtn;
@property (strong, nonatomic) UIButton *clearBtn;

@end

@implementation DemoLogsView
{
    NSMutableArray *_logsData;
    CGRect _toggleBtnOffFrame;
}

#pragma mark - TableView Protocols

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return _logsData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *RID = @"LogCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:RID];
    if(!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:RID];
    }
    cell.contentView.backgroundColor = UIColor.blackColor;
    cell.textLabel.textColor = UIColor.whiteColor;
    cell.textLabel.numberOfLines = 0;
    cell.textLabel.font = [UIFont fontWithName:@"Courier New" size:13];
    cell.textLabel.text = _logsData[indexPath.row];
    return cell;
}

static double buttonSize = 40;

- (void)setup {
    [self toggleExpand:NO];
    [self setHidden:NO];
}

- (CGRect)toggleButtonFrame {
    CGFloat y = CGRectGetMaxY(UIScreen.mainScreen.bounds) - buttonSize - 20;
    if (@available(iOS 11.0, *)) {
        y -= self.safeAreaInsets.bottom;
        y += 20;
    }
    return CGRectMake(20, y, buttonSize, buttonSize);
}

- (void)toggleExpand:(BOOL)expand {
    if(CGPointEqualToPoint(_toggleBtnOffFrame.origin, CGPointZero)) {
        _toggleBtnOffFrame = [self toggleButtonFrame];
    }
    else if(expand) {
        _toggleBtnOffFrame = self.frame;
    }
    
    _logsTb.hidden = !expand;
    _exportBtn.hidden = !expand;
    _clearBtn.hidden = !expand;
    [_toggleBtn setTitle:expand ? @"❌" : @"📝" forState:UIControlStateNormal];
    
    self.frame = expand ? UIScreen.mainScreen.bounds : _toggleBtnOffFrame;
    _logsTb.frame = self.bounds;
    _toggleBtn.frame = expand ? [self toggleButtonFrame] : self.bounds;
}

+ (NSString *)logDateString {
    static dispatch_once_t onceToken;
    static NSDateFormatter *df;
    dispatch_once(&onceToken, ^{
        df = [NSDateFormatter new];
        df.dateFormat = @"yyyy-MM-dd HH:mm:ss:SSS";
    });
    return [df stringFromDate:NSDate.date];
}

- (void)appendLog:(NSString *)format, ...{
    if(![format isKindOfClass:NSString.class]) {
        format = [NSString stringWithFormat:@"%@",format];
    }
    va_list args;
    va_start(args, format);
    NSString *logStr = [[NSString alloc] initWithFormat:format arguments:args];
    logStr = [[self.class logDateString] stringByAppendingFormat:@" %@\n",logStr];
    va_end(args);
    dispatch_async(dispatch_get_main_queue(), ^{
        @synchronized (self) {
            [self->_logsData addObject:logStr];
            NSIndexPath *lastRow = [NSIndexPath indexPathForRow:self->_logsData.count-1 inSection:0];
            [self.logsTb insertRowsAtIndexPaths:@[
                lastRow
            ] withRowAnimation:UITableViewRowAnimationNone];
            [self.logsTb scrollToRowAtIndexPath:lastRow atScrollPosition:UITableViewScrollPositionBottom animated:NO];
        }
    });
}

- (void)clearLogs {
    dispatch_async(dispatch_get_main_queue(), ^{
        @synchronized (self) {
            [self->_logsData removeAllObjects];
        }
        [self.logsTb reloadData];
    });
}

+ (instancetype)shared {
    static dispatch_once_t onceToken;
    static DemoLogsView *obj;
    dispatch_once(&onceToken, ^{
        obj = [[self alloc] initWithFrame:UIScreen.mainScreen.bounds];
    });
    return obj;
}

- (instancetype)initWithFrame:(CGRect)frame {
    if(self = [super initWithFrame:frame]) {
        self.windowLevel = UIWindowLevelStatusBar + 100;
        self.backgroundColor = UIColor.clearColor;
        
        [self addSubview:self.logsTb];
        [self addSubview:self.toggleBtn];
        [self addSubview:self.exportBtn];
        [self addSubview:self.clearBtn];
        [self setupLogs];
        
        _logsData = @[].mutableCopy;
    }
    return self;
}

+ (void)load {
    [NSUserDefaults.standardUserDefaults setValue:@"1" forKey:@"CCISMessageLogTestSwitch"];
    [NSUserDefaults.standardUserDefaults synchronize];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    {
        CGFloat y = 20;
        if (@available(iOS 11.0, *)) {
            y += self.safeAreaInsets.bottom;
        }
        _exportBtn.frame = CGRectMake(CGRectGetMaxX(self.bounds)-20-buttonSize, y, buttonSize, buttonSize);
        _clearBtn.frame = CGRectMake(CGRectGetMinX(_exportBtn.frame)-buttonSize-15, CGRectGetMinY(_exportBtn.frame), buttonSize, buttonSize);
    }
}

- (void)setupLogs {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateLogs:) name:@"CCISLogNotification" object:nil];
    
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:pan];
}

- (void)pan:(UIPanGestureRecognizer *)panGestureRecognizer{
    if(!_logsTb.hidden) {
        return;
    }
    CGFloat W = UIScreen.mainScreen.bounds.size.width;
    CGFloat H = UIScreen.mainScreen.bounds.size.height;
    //1、获得拖动位移
    CGPoint offsetPoint = [panGestureRecognizer translationInView:panGestureRecognizer.view];
    //2、清空拖动位移
    [panGestureRecognizer setTranslation:CGPointZero inView:panGestureRecognizer.view];
    //3、重新设置控件位置
    UIView *panView = panGestureRecognizer.view;
    CGFloat newX = panView.center.x+offsetPoint.x;
    CGFloat newY = panView.center.y+offsetPoint.y;
    if (newX < buttonSize/2) {
        newX = buttonSize/2;
    }
    if (newX > W - buttonSize/2) {
        newX = W - buttonSize/2;
    }
    if (newY < buttonSize/2) {
        newY = buttonSize/2;
    }
    if (newY > H - buttonSize/2) {
        newY = H - buttonSize/2;
    }
    panView.center = CGPointMake(newX, newY);
}

- (void)updateLogs:(NSNotification *)noti{
    [self appendLog:noti.userInfo[@"content"]];
}

- (void)closeView {
    [self toggleExpand:_logsTb.isHidden];
}

+ (UIButton *)makeRounderButton:(NSString *)title target:(id)target action:(SEL)selector {
    UIButton *v = [UIButton buttonWithType:UIButtonTypeCustom];
    v.frame = CGRectMake(0, 0, buttonSize, buttonSize);
    v.layer.borderColor = UIColor.blackColor.CGColor;
    v.layer.borderWidth = 1;
    v.layer.cornerRadius = buttonSize/2;
//    v.layer.shadowRadius = 5;
//    v.layer.shadowOpacity = 0.5;
//    v.layer.shadowOffset = CGSizeZero;
//    v.layer.shadowColor = UIColor.blackColor.CGColor;
    [v addTarget:target action:selector forControlEvents:UIControlEventTouchUpInside];
    [v setTitle:title forState:UIControlStateNormal];
    return v;
}

- (void)exportLogs:(UIButton *)sender {
    // 显示分享
    void(^shareLogsFile)(NSURL *file) = ^(NSURL *file){
        [self toggleExpand:NO];
        
        UIActivityViewController *avc = [[UIActivityViewController alloc] initWithActivityItems:@[
            file
        ] applicationActivities:nil];
        [avc setCompletionWithItemsHandler:^(UIActivityType __nullable activityType, BOOL completed, NSArray * __nullable returnedItems, NSError * __nullable activityError){
            if(completed) {
                [NSFileManager.defaultManager removeItemAtURL:file error:nil];
            }
        }];
        UIWindow *window = UIApplication.sharedApplication.delegate.window ?: UIApplication.sharedApplication.keyWindow;
        UIViewController *vc = window.rootViewController;
        if([vc isKindOfClass:UINavigationController.class]) {
            vc = ((UINavigationController *)vc).visibleViewController;
        }
        [vc presentViewController:avc animated:YES completion:nil];
    };
    
    // 将日志写到本地沙盒
    void(^writeLogsToLocal)(void) = ^{
        if(!self || !self->_logsData.count) { return; }
        NSMutableString *logs = @"".mutableCopy;
        for (id log in self->_logsData) {
            [logs appendString:log];
        }
        NSString *url = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        id logFile = [NSString stringWithFormat:@"%@.log",[self.class logDateString]];
        url = [url stringByAppendingPathComponent:logFile];
        BOOL written =  [[logs dataUsingEncoding:NSUTF8StringEncoding] writeToFile:url atomically:YES];
        if(written) {
            dispatch_async(dispatch_get_main_queue(), ^{
                shareLogsFile([NSURL fileURLWithPath:url]);
            });
        }
        else {
            [self appendLog:@"写日志文件到本地失败!"];
        }
    };
    
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
        writeLogsToLocal();
    });
}

- (UIButton *)clearBtn {
    if(!_clearBtn) {
        UIButton *v = [self.class makeRounderButton:@"🗑️" target:self action:@selector(clearLogs)];
        _clearBtn = v;
    }
    return _clearBtn;
}

- (UIButton *)exportBtn {
    if(!_exportBtn) {
        UIButton *v = [self.class makeRounderButton:@"📤" target:self action:@selector(exportLogs:)];
        _exportBtn = v;
    }
    return _exportBtn;
}

- (UIButton *)toggleBtn {
    if(!_toggleBtn) {
        UIButton *v = [self.class makeRounderButton:@"" target:self action:@selector(closeView)];
        _toggleBtn = v;
    }
    return _toggleBtn;
}

- (UITableView *)logsTb {
    if(!_logsTb) {
        UITableView *tv = [[UITableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
        tv.rowHeight = UITableViewAutomaticDimension;
        tv.estimatedRowHeight = 44;
        tv.delegate = self;
        tv.dataSource = self;
        tv.allowsSelection = NO;
        tv.backgroundColor = UIColor.blackColor;
        tv.contentInset = UIEdgeInsetsMake(buttonSize, 0, buttonSize, 0);
        _logsTb = tv;
    }
    return _logsTb;
}

@end
上一篇下一篇

猜你喜欢

热点阅读