iOS | Build a Log Explore View

@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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,495评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,469评论 2 369
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 140,825评论 0 318
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 51,974评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,849评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 45,990评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,415评论 3 380
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,125评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,351评论 1 288
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,474评论 2 307
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,249评论 1 324
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,119评论 3 310
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,496评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,838评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,118评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,366评论 2 340
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,573评论 2 335