KISS

Keep It Simple Stupid

Log to UITextView on iOS

| comments

Update on 2013-10-22: I’ve slightly updated the class for the latest CocoaLumberjack. Also, you can check out a sample project that uses this class here: https://github.com/pluton8/TextViewLoggerSample.

CocoaLumberjack is a nice library for logging on iOS, with many more features than the standard NSLog method, and allowing to customize logging to your needs. It’s not always convenient/possible to run your iOS app from Xcode to check the app’s logs, so I’ve created a simple logger that prints the messages to a UITextView UI component.

Here’s the code:

(UITextViewLogger.h) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//
//  UITextViewLogger.h
//
//  Created by Eugene Nikolsky on 8/8/12.
//  Copyright (c) 2012–2013. All rights reserved.
//

#import "DDLog.h"

/*
 * A logger for CocoaLumberjack that outputs the messages to
 * a UITextView.
 * 
 * If textView is not set up, the logs will be cached and then
 * flushed when it's set up.
 */
@interface UITextViewLogger : DDAbstractLogger <DDLogger>

// Text view where to print logs to.
@property (nonatomic, weak) UITextView *textView;
// Specifies if the text view should be automatically scrolled
// to bottom after appending any log message.
@property (nonatomic, assign) BOOL autoScrollsToBottom;

@end
(UITextViewLogger.m) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//
//  UITextViewLogger.m
//
//  Created by Eugene Nikolsky on 8/8/12.
//  Copyright (c) 2012–2013. All rights reserved.
//

#import "UITextViewLogger.h"

@interface UITextViewLogger ()

@property (nonatomic, strong) NSMutableArray *logMsgCache;

@end


@implementation UITextViewLogger

@synthesize textView = _textView;
@synthesize autoScrollsToBottom = _autoScrollsToBottom;
@synthesize logMsgCache = _logMsgCache;

#pragma mark - Init

- (id)init {
  if (self = [super init]) {
      _autoScrollsToBottom = YES;
  }
  return self;
}

#pragma mark - Private Stuff

- (void)appendTextViewString:(NSString *)string {
  NSAssert(self.textView != nil, @"self.textView is nil");
  
  dispatch_async(dispatch_get_main_queue(), ^(void) {
      NSString *newText = [self.textView.text stringByAppendingString:string];
      self.textView.text = newText;
      
      if (self.autoScrollsToBottom) {
          [self.textView scrollRangeToVisible:NSMakeRange(newText.length, 0)];
      }
  });
}

#pragma mark - DDLogger

- (void)logMessage:(DDLogMessage *)logMessage {
  NSString *logMsg = logMessage->logMsg;
  if (formatter) {
      logMsg = [formatter formatLogMessage:formatter];
  }
  
  if (logMsg) {
      /* if textView is available, write to it,
      otherwise cache it */
      if (self.textView) {
          [self appendTextViewString:[NSString stringWithFormat:@"\n%@", logMsg]];
      } else {
          if (!self.logMsgCache) {
              self.logMsgCache = [NSMutableArray array];
          }
          
          [self.logMsgCache addObject:logMsg];
      }
  }
}

#pragma mark - Getters & Setters

- (void)setTextView:(UITextView *)textView {
  if (_textView != textView) {
      _textView = textView;
      
      NSString *entireLog = [self.logMsgCache componentsJoinedByString:@"\n"];
      [self.logMsgCache removeAllObjects];
      
      [self appendTextViewString:entireLog];
  }
}

@end

Nota bene: if your project is not using ARC, don’t forget to enable the -fobjc-arc compilation flag for the UITextViewLogger.m file in the your target > Build Phases > Compile Sources (http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011226-CH1-SW17, “Can I opt out of ARC for specific files?” question). Otherwise, you may get a memory leak or two.

Usage is simple. E.g., in your AppDelegate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// import the file
#import "UITextViewLogger.h"

// …

- (BOOL)application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // create the logger
    UITextViewLogger *textViewLogger = [[[UITextViewLogger alloc] init] autorelease];
    textViewLogger.autoScrollsToBottom = YES;
    [DDLog addLogger:textViewLogger];

    // get your view controller
    self.viewController = [[[ViewController alloc] initWithNibName:@"ViewController"
                                                            bundle:nil] autorelease];

    // set up the text view for logging
    textViewLogger.textView = self.viewController.textViewLog;

    // …
}

With this we get a chance to watch the logs in the app in realtime. Of course, you can easily attach a DDFileLogger to dump the messages to a file as well.

Important: if the app generates a great deal of logs, the logger will significantly reduce the performance, especially when the autoScrollsToBottom flag is set.

Comments