Przeglądaj źródła

Add table view controller and cell subclasses to show network history

Ryan Olson 11 lat temu
rodzic
commit
3c8ac29ebe

+ 10 - 0
Classes/Global State Explorers/FLEXGlobalsTableViewController.m

@@ -17,10 +17,12 @@
 #import "FLEXGlobalsTableViewControllerEntry.h"
 #import "FLEXGlobalsTableViewControllerEntry.h"
 #import "FLEXManager+Private.h"
 #import "FLEXManager+Private.h"
 #import "FLEXSystemLogTableViewController.h"
 #import "FLEXSystemLogTableViewController.h"
+#import "FLEXNetworkHistoryTableViewController.h"
 
 
 static __weak UIWindow *s_applicationWindow = nil;
 static __weak UIWindow *s_applicationWindow = nil;
 
 
 typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
 typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
+    FLEXGlobalsRowNetworkHistory,
     FLEXGlobalsRowSystemLog,
     FLEXGlobalsRowSystemLog,
     FLEXGlobalsRowLiveObjects,
     FLEXGlobalsRowLiveObjects,
     FLEXGlobalsRowFileBrowser,
     FLEXGlobalsRowFileBrowser,
@@ -178,6 +180,14 @@ typedef NS_ENUM(NSUInteger, FLEXGlobalsRow) {
                 };
                 };
                 break;
                 break;
 
 
+            case FLEXGlobalsRowNetworkHistory:
+                titleFuture = ^{
+                    return @"📡  Network History";
+                };
+                viewControllerFuture = ^{
+                    return [[FLEXNetworkHistoryTableViewController alloc] init];
+                };
+                break;
             case FLEXGlobalsRowCount:
             case FLEXGlobalsRowCount:
                 break;
                 break;
         }
         }

+ 13 - 0
Classes/Network/FLEXNetworkHistoryTableViewController.h

@@ -0,0 +1,13 @@
+//
+//  FLEXNetworkHistoryTableViewController.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 2/8/15.
+//  Copyright (c) 2015 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXNetworkHistoryTableViewController : UITableViewController
+
+@end

+ 89 - 0
Classes/Network/FLEXNetworkHistoryTableViewController.m

@@ -0,0 +1,89 @@
+//
+//  FLEXNetworkHistoryTableViewController.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 2/8/15.
+//  Copyright (c) 2015 Flipboard. All rights reserved.
+//
+
+#import "FLEXNetworkHistoryTableViewController.h"
+#import "FLEXNetworkTransaction.h"
+#import "FLEXNetworkTransactionTableViewCell.h"
+#import "FLEXNetworkRecorder.h"
+
+@interface FLEXNetworkHistoryTableViewController ()
+
+/// Backing model
+@property (nonatomic, copy) NSArray *networkTransactions;
+
+@end
+
+@implementation FLEXNetworkHistoryTableViewController
+
+- (instancetype)initWithStyle:(UITableViewStyle)style
+{
+    self = [super initWithStyle:style];
+    if (self) {
+        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNewTransactionRecordedNotification:) name:kFLEXNetworkRecorderNewTransactionNotification object:nil];
+        self.title = @"📡  Network";
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)viewDidLoad
+{
+    [super viewDidLoad];
+
+    [self.tableView registerClass:[FLEXNetworkTransactionTableViewCell class] forCellReuseIdentifier:kFLEXNetworkTransactionCellIdentifier];
+    self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+    self.tableView.rowHeight = [FLEXNetworkTransactionTableViewCell preferredCellHeight];
+
+    [self updateTransactions];
+}
+
+- (void)updateTransactions
+{
+    self.networkTransactions = [[FLEXNetworkRecorder defaultRecorder] networkTransactions];
+}
+
+- (void)handleNewTransactionRecordedNotification:(NSNotification *)notification
+{
+    // Note that these notifications may be posted from a background thread.
+    dispatch_async(dispatch_get_main_queue(), ^{
+        [self updateTransactions];
+        [self.tableView reloadData];
+    });
+}
+
+#pragma mark - Table view data source
+
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
+{
+    return 1;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    return [self.networkTransactions count];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    FLEXNetworkTransactionTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kFLEXNetworkTransactionCellIdentifier forIndexPath:indexPath];
+    cell.transaction = [self.networkTransactions objectAtIndex:indexPath.row];
+
+    if (indexPath.row % 2 == 0) {
+        cell.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1.0];
+    } else {
+        cell.backgroundColor = [UIColor whiteColor];
+    }
+
+    return cell;
+}
+
+@end

+ 21 - 0
Classes/Network/FLEXNetworkTransactionTableViewCell.h

@@ -0,0 +1,21 @@
+//
+//  FLEXNetworkTransactionTableViewCell.h
+//  Flipboard
+//
+//  Created by Ryan Olson on 2/8/15.
+//  Copyright (c) 2015 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+extern NSString *const kFLEXNetworkTransactionCellIdentifier;
+
+@class FLEXNetworkTransaction;
+
+@interface FLEXNetworkTransactionTableViewCell : UITableViewCell
+
+@property (nonatomic, strong) FLEXNetworkTransaction *transaction;
+
++ (CGFloat)preferredCellHeight;
+
+@end

+ 183 - 0
Classes/Network/FLEXNetworkTransactionTableViewCell.m

@@ -0,0 +1,183 @@
+//
+//  FLEXNetworkTransactionTableViewCell.m
+//  Flipboard
+//
+//  Created by Ryan Olson on 2/8/15.
+//  Copyright (c) 2015 Flipboard. All rights reserved.
+//
+
+#import "FLEXNetworkTransactionTableViewCell.h"
+#import "FLEXNetworkTransaction.h"
+#import "FLEXUtility.h"
+#import "FLEXResources.h"
+
+NSString *const kFLEXNetworkTransactionCellIdentifier = @"kFLEXNetworkTransactionCellIdentifier";
+
+@interface FLEXNetworkTransactionTableViewCell ()
+
+@property (nonatomic, strong) UIImageView *thumbnailImageView;
+@property (nonatomic, strong) UILabel *nameLabel;
+@property (nonatomic, strong) UILabel *pathLabel;
+@property (nonatomic, strong) UILabel *transactionDetailsLabel;
+
+@end
+
+@implementation FLEXNetworkTransactionTableViewCell
+
+- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
+{
+    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
+    if (self) {
+        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+
+        self.nameLabel = [[UILabel alloc] init];
+        self.nameLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
+        [self.contentView addSubview:self.nameLabel];
+
+        self.pathLabel = [[UILabel alloc] init];
+        self.pathLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
+        self.pathLabel.textColor = [UIColor colorWithWhite:0.4 alpha:1.0];
+        [self.contentView addSubview:self.pathLabel];
+
+        self.thumbnailImageView = [[UIImageView alloc] init];
+        self.thumbnailImageView.layer.borderColor = [[UIColor blackColor] CGColor];
+        self.thumbnailImageView.layer.borderWidth = 1.0;
+        self.thumbnailImageView.contentMode = UIViewContentModeScaleAspectFit;
+        [self.contentView addSubview:self.thumbnailImageView];
+
+        self.transactionDetailsLabel = [[UILabel alloc] init];
+        self.transactionDetailsLabel.font = [FLEXUtility defaultFontOfSize:10.0];
+        self.transactionDetailsLabel.textColor = [UIColor colorWithWhite:0.65 alpha:1.0];
+        [self.contentView addSubview:self.transactionDetailsLabel];
+    }
+    return self;
+}
+
+- (void)setTransaction:(FLEXNetworkTransaction *)transaction
+{
+    if (_transaction != transaction) {
+        _transaction = transaction;
+        [self setNeedsLayout];
+    }
+}
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+
+    const CGFloat kVerticalPadding = 8.0;
+    const CGFloat kLeftPadding = 10.0;
+    const CGFloat kImageDimension = 32.0;
+
+    CGFloat thumbnailOriginY = round((self.contentView.bounds.size.height - kImageDimension) / 2.0);
+    self.thumbnailImageView.frame = CGRectMake(kLeftPadding, thumbnailOriginY, kImageDimension, kImageDimension);
+    self.thumbnailImageView.image = self.transaction.responseThumbnail;
+
+    CGFloat textOriginX = CGRectGetMaxX(self.thumbnailImageView.frame) + kLeftPadding;
+    CGFloat availableTextWidth = self.contentView.bounds.size.width - textOriginX;
+
+    self.nameLabel.text = [self nameLabelText];
+    CGSize nameLabelPreferredSize = [self.nameLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
+    self.nameLabel.frame = CGRectMake(textOriginX, kVerticalPadding, availableTextWidth, nameLabelPreferredSize.height);
+    self.nameLabel.textColor = self.transaction.error ? [UIColor redColor] : [UIColor blackColor];
+
+    self.pathLabel.text = [self pathLabelText];
+    CGSize pathLabelPreferredSize = [self.pathLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
+    CGFloat pathLabelOriginY = ceil((self.contentView.bounds.size.height - pathLabelPreferredSize.height) / 2.0);
+    self.pathLabel.frame = CGRectMake(textOriginX, pathLabelOriginY, availableTextWidth, pathLabelPreferredSize.height);
+
+    self.transactionDetailsLabel.text = [self transactionDetailsLabelText];
+    CGSize transactionLabelPreferredSize = [self.transactionDetailsLabel sizeThatFits:CGSizeMake(availableTextWidth, CGFLOAT_MAX)];
+    CGFloat transactionDetailsOriginX = textOriginX;
+    CGFloat transactionDetailsLabelOriginY = CGRectGetMaxY(self.contentView.bounds) - kVerticalPadding - transactionLabelPreferredSize.height;
+    CGFloat transactionDetailsLabelWidth = self.contentView.bounds.size.width - transactionDetailsOriginX;
+    self.transactionDetailsLabel.frame = CGRectMake(transactionDetailsOriginX, transactionDetailsLabelOriginY, transactionDetailsLabelWidth, transactionLabelPreferredSize.height);
+}
+
+- (NSString *)nameLabelText
+{
+    NSURL *url = self.transaction.request.URL;
+    NSString *name = [url lastPathComponent];
+    NSString *query = [url query];
+    if (query) {
+        name = [name stringByAppendingFormat:@"?%@", query];
+    }
+    return name;
+}
+
+- (NSString *)pathLabelText
+{
+    NSURL *url = self.transaction.request.URL;
+    NSMutableArray *mutablePathComponents = [[url pathComponents] mutableCopy];
+    if ([mutablePathComponents count] > 0) {
+        [mutablePathComponents removeLastObject];
+    }
+    NSString *path = [url host];
+    for (NSString *pathComponent in mutablePathComponents) {
+        path = [path stringByAppendingPathComponent:pathComponent];
+    }
+    return path;
+}
+
+- (NSString *)transactionDetailsLabelText
+{
+    NSMutableArray *detailComponents = [NSMutableArray array];
+
+    NSString *timestamp = [[self class] timestampStringFromRequestDate:self.transaction.startTime];
+    [detailComponents addObject:timestamp];
+
+    // Omit method for GET (assumed as default)
+    NSString *httpMethod = self.transaction.request.HTTPMethod;
+    if (httpMethod) {
+        [detailComponents addObject:httpMethod];
+    }
+
+    if (self.transaction.transactionState == FLEXNetworkTransactionStateFinished || self.transaction.transactionState == FLEXNetworkTransactionStateFailed) {
+        if ([self.transaction.response isKindOfClass:[NSHTTPURLResponse class]]) {
+            NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)self.transaction.response;
+            NSString *statusCodeDescription = nil;
+            if (httpResponse.statusCode == 200) {
+                // Prefer OK to the default "no error"
+                statusCodeDescription = @"OK";
+            } else {
+                statusCodeDescription = [NSHTTPURLResponse localizedStringForStatusCode:httpResponse.statusCode];
+            }
+            NSString *httpResponseString = [NSString stringWithFormat:@"%ld %@", (long)httpResponse.statusCode, statusCodeDescription];
+            [detailComponents addObject:httpResponseString];
+        }
+
+        if (self.transaction.receivedDataLength > 0) {
+            NSString *responseSize = [NSByteCountFormatter stringFromByteCount:self.transaction.receivedDataLength countStyle:NSByteCountFormatterCountStyleBinary];
+            [detailComponents addObject:responseSize];
+        }
+
+        NSString *totalDuration = [FLEXUtility stringFromRequestDuration:self.transaction.duration];
+        NSString *latency = [FLEXUtility stringFromRequestDuration:self.transaction.latency];
+        NSString *duration = [NSString stringWithFormat:@"%@ (%@)", totalDuration, latency];
+        [detailComponents addObject:duration];
+    } else {
+        // Unstarted, Awaiting Response, Receiving Data, etc.
+        NSString *state = [FLEXNetworkTransaction readableStringFromTransactionState:self.transaction.transactionState];
+        [detailComponents addObject:state];
+    }
+
+    return [detailComponents componentsJoinedByString:@" ・ "];
+}
+
++ (NSString *)timestampStringFromRequestDate:(NSDate *)date
+{
+    static NSDateFormatter *dateFormatter = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        dateFormatter = [[NSDateFormatter alloc] init];
+        dateFormatter.dateFormat = @"HH:mm:ss";
+    });
+    return [dateFormatter stringFromDate:date];
+}
+
++ (CGFloat)preferredCellHeight
+{
+    return 65.0;
+}
+
+@end