Просмотр исходного кода

Merge pull request #93 from tttpeng/master

Add a sqlite database browser
Ryan Olson лет назад: 10
Родитель
Сommit
21672e6f8d
19 измененных файлов с 1293 добавлено и 4 удалено
  1. 26 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXDatabaseManager.h
  2. 195 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXDatabaseManager.m
  3. 48 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXMultiColumnTableView.h
  4. 341 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXMultiColumnTableView.m
  5. 24 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableColumnHeader.h
  6. 60 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableColumnHeader.m
  7. 27 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentCell.h
  8. 66 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentCell.m
  9. 16 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.h
  10. 184 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.m
  11. 17 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableLeftCell.h
  12. 35 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableLeftCell.m
  13. 15 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableListViewController.h
  14. 82 0
      Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableListViewController.m
  15. 21 0
      Classes/GlobalStateExplorers/DatabaseBrowser/LICENSE
  16. 5 1
      Classes/GlobalStateExplorers/FLEXFileBrowserTableViewController.m
  17. 55 1
      Example/UICatalog.xcodeproj/project.pbxproj
  18. 74 2
      FLEX.xcodeproj/project.pbxproj
  19. 2 0
      README.md

+ 26 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXDatabaseManager.h

@@ -0,0 +1,26 @@
+//
+//  PTDatabaseManager.h
+//  Derived from:
+//
+//  FMDatabase.h
+//  FMDB( https://github.com/ccgus/fmdb )
+//
+//  Created by Peng Tao on 15/11/23.
+//
+//  Licensed to Flying Meat Inc. under one or more contributor license agreements.
+//  See the LICENSE file distributed with this work for the terms under
+//  which Flying Meat Inc. licenses this file to you.
+
+#import <Foundation/Foundation.h>
+
+@interface FLEXDatabaseManager : NSObject
+
+
+- (instancetype)initWithPath:(NSString*)aPath;
+
+- (BOOL)open;
+- (NSArray *)queryAllTables;
+- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName;
+- (NSArray *)queryAllDataWithTableName:(NSString *)tableName;
+
+@end

+ 195 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXDatabaseManager.m

@@ -0,0 +1,195 @@
+//
+//  PTDatabaseManager.m
+//  PTDatabaseReader
+//
+//  Created by Peng Tao on 15/11/23.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+
+
+#import "FLEXDatabaseManager.h"
+#import <sqlite3.h>
+
+
+static NSString *const QUERY_TABLENAMES_SQL = @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name";
+
+
+@implementation FLEXDatabaseManager
+{
+    sqlite3* _db;
+    NSString* _databasePath;
+}
+
+
+- (instancetype)initWithPath:(NSString*)aPath
+{
+    self = [super init];
+    
+    if (self) {
+        _databasePath = [aPath copy];
+    }
+    return self;
+}
+
+
+- (BOOL)open {
+    if (_db) {
+        return YES;
+    }
+    int err = sqlite3_open([_databasePath UTF8String], &_db);
+    if(err != SQLITE_OK) {
+        NSLog(@"error opening!: %d", err);
+        return NO;
+    }
+    return YES;
+}
+
+- (BOOL)close {
+    if (!_db) {
+        return YES;
+    }
+    
+    int  rc;
+    BOOL retry;
+    BOOL triedFinalizingOpenStatements = NO;
+    
+    do {
+        retry   = NO;
+        rc      = sqlite3_close(_db);
+        if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) {
+            if (!triedFinalizingOpenStatements) {
+                triedFinalizingOpenStatements = YES;
+                sqlite3_stmt *pStmt;
+                while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) {
+                    NSLog(@"Closing leaked statement");
+                    sqlite3_finalize(pStmt);
+                    retry = YES;
+                }
+            }
+        }
+        else if (SQLITE_OK != rc) {
+            NSLog(@"error closing!: %d", rc);
+        }
+    }
+    while (retry);
+    
+    _db = nil;
+    return YES;
+}
+
+
+- (NSArray *)queryAllTables
+{
+    return [self executeQuery:QUERY_TABLENAMES_SQL];
+}
+
+- (NSArray *)queryAllColumnsWithTableName:(NSString *)tableName
+{
+    NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
+    NSArray *resultArray =  [self executeQuery:sql];
+    NSMutableArray *array = [NSMutableArray array];
+    for (NSDictionary *dict in resultArray) {
+        [array addObject:dict[@"name"]];
+    }
+    return array;
+}
+
+- (NSArray *)queryAllDataWithTableName:(NSString *)tableName
+{
+    NSString *sql = [NSString stringWithFormat:@"SELECT * FROM %@",tableName];
+    return [self executeQuery:sql];
+}
+
+#pragma mark -
+#pragma mark - Private
+
+- (NSArray *)executeQuery:(NSString *)sql
+{
+    [self open];
+    NSMutableArray *resultArray = [NSMutableArray array];
+    sqlite3_stmt *pstmt;
+    if (sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pstmt, 0) == SQLITE_OK) {
+        while (sqlite3_step(pstmt) == SQLITE_ROW) {
+            NSUInteger num_cols = (NSUInteger)sqlite3_data_count(pstmt);
+            if (num_cols > 0) {
+                NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols];
+                
+                int columnCount = sqlite3_column_count(pstmt);
+                
+                int columnIdx = 0;
+                for (columnIdx = 0; columnIdx < columnCount; columnIdx++) {
+                    
+                    NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name(pstmt, columnIdx)];
+                    id objectValue = [self objectForColumnIndex:columnIdx stmt:pstmt];
+                    [dict setObject:objectValue forKey:columnName];
+                }
+                [resultArray addObject:dict];
+            }
+        }
+    }
+    [self close];
+    return resultArray;
+}
+
+
+- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
+    int columnType = sqlite3_column_type(stmt, columnIdx);
+    
+    id returnValue = nil;
+    
+    if (columnType == SQLITE_INTEGER) {
+        returnValue =  [NSNumber numberWithLongLong:sqlite3_column_int64(stmt, columnIdx)];
+    }
+    else if (columnType == SQLITE_FLOAT) {
+        returnValue = [NSNumber numberWithDouble:sqlite3_column_double(stmt, columnIdx)];
+    }
+    else if (columnType == SQLITE_BLOB) {
+        returnValue = [self dataForColumnIndex:columnIdx stmt:stmt];
+    }
+    else {
+        //default to a string for everything else
+        returnValue = [self stringForColumnIndex:columnIdx stmt:stmt];
+    }
+    
+    if (returnValue == nil) {
+        returnValue = [NSNull null];
+    }
+    
+    return returnValue;
+}
+
+- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
+    
+    if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    const char *c = (const char *)sqlite3_column_text(stmt, columnIdx);
+    
+    if (!c) {
+        // null row.
+        return nil;
+    }
+    
+    return [NSString stringWithUTF8String:c];
+}
+
+- (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt{
+    
+    if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
+        return nil;
+    }
+    
+    const char *dataBuffer = sqlite3_column_blob(stmt, columnIdx);
+    int dataSize = sqlite3_column_bytes(stmt, columnIdx);
+    
+    if (dataBuffer == NULL) {
+        return nil;
+    }
+    
+    return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize];
+}
+
+
+@end

+ 48 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXMultiColumnTableView.h

@@ -0,0 +1,48 @@
+//
+//  PTMultiColumnTableView.h
+//  PTMultiColumnTableViewDemo
+//
+//  Created by Peng Tao on 15/11/16.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "FLEXTableColumnHeader.h"
+
+@class FLEXMultiColumnTableView;
+
+@protocol FLEXMultiColumnTableViewDelegate <NSObject>
+
+@required
+- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text;
+- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType;
+
+@end
+
+@protocol FLEXMultiColumnTableViewDataSource <NSObject>
+
+@required
+
+- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView;
+- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView;
+- (NSString *)columnNameInColumn:(NSInteger)column;
+- (NSString *)rowNameInRow:(NSInteger)row;
+- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row;
+- (NSArray *)contentAtRow:(NSInteger)row;
+
+- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView widthForContentCellInColumn:(NSInteger)column;
+- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView heightForContentCellInRow:(NSInteger)row;
+- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
+- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView;
+
+@end
+
+
+@interface FLEXMultiColumnTableView : UIView
+
+@property (nonatomic, weak) id<FLEXMultiColumnTableViewDataSource>dataSource;
+@property (nonatomic, weak) id<FLEXMultiColumnTableViewDelegate>delegate;
+
+- (void)reloadData;
+
+@end

+ 341 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXMultiColumnTableView.m

@@ -0,0 +1,341 @@
+//
+//  PTMultiColumnTableView.m
+//  PTMultiColumnTableViewDemo
+//
+//  Created by Peng Tao on 15/11/16.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import "FLEXMultiColumnTableView.h"
+#import "FLEXTableContentCell.h"
+#import "FLEXTableLeftCell.h"
+
+@interface FLEXMultiColumnTableView ()
+<UITableViewDataSource, UITableViewDelegate,UIScrollViewDelegate, FLEXTableContentCellDelegate>
+
+@property (nonatomic, strong) UIScrollView *contentScrollView;
+@property (nonatomic, strong) UIScrollView *headerScrollView;
+@property (nonatomic, strong) UITableView  *leftTableView;
+@property (nonatomic, strong) UITableView  *contentTableView;
+@property (nonatomic, strong) UIView       *leftHeader;
+
+@property (nonatomic, strong) NSDictionary *sortStatusDict;
+@property (nonatomic, strong) NSArray *rowData;
+@end
+
+static const CGFloat kColumnMargin = 1;
+
+@implementation FLEXMultiColumnTableView
+
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        [self loadUI];
+    }
+    return self;
+}
+
+- (void)didMoveToSuperview
+{
+    [super didMoveToSuperview];
+    [self reloadData];
+}
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+    
+    CGFloat width  = self.frame.size.width;
+    CGFloat height = self.frame.size.height;
+    CGFloat topheaderHeight = [self topHeaderHeight];
+    CGFloat leftHeaderWidth = [self leftHeaderWidth];
+    
+    CGFloat contentWidth = 0.0;
+    NSInteger rowsCount = [self numberOfColumns];
+    for (int i = 0; i < rowsCount; i++) {
+        contentWidth += [self contentWidthForColumn:i];
+    }
+    
+    self.leftTableView.frame           = CGRectMake(0, topheaderHeight, leftHeaderWidth, height - topheaderHeight);
+    self.headerScrollView.frame        = CGRectMake(leftHeaderWidth, 0, width - leftHeaderWidth, topheaderHeight);
+    self.headerScrollView.contentSize  = CGSizeMake( self.contentTableView.frame.size.width, self.headerScrollView.frame.size.height);
+    self.contentTableView.frame        = CGRectMake(0, 0, contentWidth + [self numberOfColumns] * [self columnMargin] , height - topheaderHeight);
+    self.contentScrollView.frame       = CGRectMake(leftHeaderWidth, topheaderHeight, width - leftHeaderWidth, height - topheaderHeight);
+    self.contentScrollView.contentSize = self.contentTableView.frame.size;
+    self.leftHeader.frame              = CGRectMake(0, 0, [self leftHeaderWidth], [self topHeaderHeight]);
+}
+
+
+- (void)loadUI
+{
+    [self loadHeaderScrollView];
+    [self loadContentScrollView];
+    [self loadLeftView];
+}
+
+- (void)reloadData
+{
+    [self loadLeftViewData];
+    [self loadContentData];
+    [self loadHeaderData];
+}
+
+#pragma mark - UI
+
+- (void)loadHeaderScrollView
+{
+    UIScrollView *headerScrollView = [[UIScrollView alloc] init];
+    headerScrollView.delegate      = self;
+    self.headerScrollView          = headerScrollView;
+    self.headerScrollView.backgroundColor =  [UIColor colorWithWhite:0.803 alpha:0.850];
+    
+    [self addSubview:headerScrollView];
+}
+
+- (void)loadContentScrollView
+{
+    
+    UIScrollView *scrollView = [[UIScrollView alloc] init];
+    scrollView.bounces       = NO;
+    scrollView.delegate      = self;
+    
+    UITableView *tableView   = [[UITableView alloc] init];
+    tableView.delegate       = self;
+    tableView.dataSource     = self;
+    tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+    
+    [self addSubview:scrollView];
+    [scrollView addSubview:tableView];
+    
+    self.contentScrollView = scrollView;
+    self.contentTableView    = tableView;
+    
+}
+
+- (void)loadLeftView
+{
+    UITableView *leftTableView = [[UITableView alloc] init];
+    leftTableView.delegate       = self;
+    leftTableView.dataSource     = self;
+    leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
+    self.leftTableView           = leftTableView;
+    [self addSubview:leftTableView];
+    
+    UIView *leftHeader = [[UIView alloc] init];
+    leftHeader.backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.668];
+    self.leftHeader            = leftHeader;
+    [self addSubview:leftHeader];
+    
+}
+
+
+#pragma mark - Data
+
+- (void)loadHeaderData
+{
+    NSArray *subviews = self.headerScrollView.subviews;
+    
+    for (UIView *subview in subviews) {
+        [subview removeFromSuperview];
+    }
+    CGFloat x = 0.0;
+    CGFloat w = 0.0;
+    for (int i = 0; i < [self numberOfColumns] ; i++) {
+        w = [self contentWidthForColumn:i] + [self columnMargin];
+        
+        FLEXTableColumnHeader *cell = [[FLEXTableColumnHeader alloc] initWithFrame:CGRectMake(x, 0, w, [self topHeaderHeight] - 1)];
+        cell.label.text = [self columnTitleForColumn:i];
+        [self.headerScrollView addSubview:cell];
+        
+        FLEXTableColumnHeaderSortType type = [self.sortStatusDict[[self columnTitleForColumn:i]] integerValue];
+        [cell changeSortStatusWithType:type];
+        
+        UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:self
+                                                                                  action:@selector(contentHeaderTap:)];
+        [cell addGestureRecognizer:gesture];
+        cell.userInteractionEnabled = YES;
+        
+        x = x + w;
+    }
+}
+
+- (void)contentHeaderTap:(UIGestureRecognizer *)gesture
+{
+    FLEXTableColumnHeader *header = (FLEXTableColumnHeader *)gesture.view;
+    NSString *string = header.label.text;
+    FLEXTableColumnHeaderSortType currentType = [self.sortStatusDict[string] integerValue];
+    FLEXTableColumnHeaderSortType newType ;
+    
+    switch (currentType) {
+        case FLEXTableColumnHeaderSortTypeNone:
+            newType = FLEXTableColumnHeaderSortTypeAsc;
+            break;
+        case FLEXTableColumnHeaderSortTypeAsc:
+            newType = FLEXTableColumnHeaderSortTypeDesc;
+            break;
+        case FLEXTableColumnHeaderSortTypeDesc:
+            newType = FLEXTableColumnHeaderSortTypeAsc;
+            break;
+    }
+    
+    self.sortStatusDict = @{header.label.text : @(newType)};
+    [header changeSortStatusWithType:newType];
+    [self.delegate multiColumnTableView:self didTapHeaderWithText:string sortType:newType];
+    
+}
+
+- (void)loadContentData
+{
+    [self.contentTableView reloadData];
+}
+
+- (void)loadLeftViewData
+{
+    [self.leftTableView reloadData];
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView
+         cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    UIColor *backgroundColor = [UIColor whiteColor];
+    if (indexPath.row % 2 != 0) {
+        backgroundColor = [UIColor colorWithWhite:0.950 alpha:0.750];
+    }
+    
+    if (tableView != self.leftTableView) {
+        self.rowData = [self.dataSource contentAtRow:indexPath.row];
+        FLEXTableContentCell *cell = [FLEXTableContentCell cellWithTableView:tableView
+                                                                columnNumber:[self numberOfColumns]];
+        cell.contentView.backgroundColor = backgroundColor;
+        cell.delegate = self;
+        
+        for (int i = 0 ; i < cell.labels.count; i++) {
+            
+            UILabel *label  = cell.labels[i];
+            label.textColor = [UIColor blackColor];
+            
+            NSString *content = [NSString stringWithFormat:@"%@",self.rowData[i]];
+            if ([content isEqualToString:@"<null>"]) {
+                label.textColor = [UIColor lightGrayColor];
+                content = @"NULL";
+            }
+            label.text            = content;
+            label.backgroundColor = backgroundColor;
+        }
+        return cell;
+    }
+    else {
+        FLEXTableLeftCell *cell          = [FLEXTableLeftCell cellWithTableView:tableView];
+        cell.contentView.backgroundColor = backgroundColor;
+        cell.titlelabel.text             = [self rowTitleForRow:indexPath.row];
+        return cell;
+    }
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    return [self.dataSource numberOfRowsInTableView:self];
+}
+
+
+- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    return [self.dataSource multiColumnTableView:self heightForContentCellInRow:indexPath.row];
+}
+
+
+- (void)scrollViewDidScroll:(UIScrollView *)scrollView
+{
+    if (scrollView == self.contentScrollView) {
+        self.headerScrollView.contentOffset = scrollView.contentOffset;
+    }
+    else if (scrollView == self.headerScrollView) {
+        self.contentScrollView.contentOffset = scrollView.contentOffset;
+    }
+    else if (scrollView == self.leftTableView) {
+        self.contentTableView.contentOffset = scrollView.contentOffset;
+    }
+    else if (scrollView == self.contentTableView) {
+        self.leftTableView.contentOffset = scrollView.contentOffset;
+    }
+}
+
+#pragma mark -
+#pragma mark UITableView Delegate
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    if (tableView == self.leftTableView) {
+        [self.contentTableView selectRowAtIndexPath:indexPath
+                                           animated:NO
+                                     scrollPosition:UITableViewScrollPositionNone];
+    }
+    else if (tableView == self.contentTableView) {
+        [self.leftTableView selectRowAtIndexPath:indexPath
+                                        animated:NO
+                                  scrollPosition:UITableViewScrollPositionNone];
+    }
+}
+
+#pragma mark -
+#pragma mark DataSource Accessor
+
+- (NSInteger)numberOfrows
+{
+    return [self.dataSource numberOfRowsInTableView:self];
+}
+
+- (NSInteger)numberOfColumns
+{
+    return [self.dataSource numberOfColumnsInTableView:self];
+}
+
+- (NSString *)columnTitleForColumn:(NSInteger)column
+{
+    return [self.dataSource columnNameInColumn:column];
+}
+
+- (NSString *)rowTitleForRow:(NSInteger)row
+{
+    return [self.dataSource rowNameInRow:row];
+}
+
+- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row;
+{
+    return [self.dataSource contentAtColumn:column row:row];
+}
+
+- (CGFloat)contentWidthForColumn:(NSInteger)column
+{
+    return [self.dataSource multiColumnTableView:self widthForContentCellInColumn:column];
+}
+
+- (CGFloat)contentHeightForRow:(NSInteger)row
+{
+    return [self.dataSource multiColumnTableView:self heightForContentCellInRow:row];
+}
+
+- (CGFloat)topHeaderHeight
+{
+    return [self.dataSource heightForTopHeaderInTableView:self];
+}
+
+- (CGFloat)leftHeaderWidth
+{
+    return [self.dataSource widthForLeftHeaderInTableView:self];
+}
+
+- (CGFloat)columnMargin
+{
+    return kColumnMargin;
+}
+
+
+- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text
+{
+    [self.delegate multiColumnTableView:self didTapLabelWithText:text];
+}
+
+@end

+ 24 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableColumnHeader.h

@@ -0,0 +1,24 @@
+//
+//  FLEXTableContentHeaderCell.h
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/26.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+typedef NS_ENUM(NSUInteger, FLEXTableColumnHeaderSortType) {
+    FLEXTableColumnHeaderSortTypeNone = 0,
+    FLEXTableColumnHeaderSortTypeAsc,
+    FLEXTableColumnHeaderSortTypeDesc,
+};
+
+@interface FLEXTableColumnHeader : UIView
+
+@property (nonatomic, strong) UILabel *label;
+
+- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type;
+
+@end
+

+ 60 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableColumnHeader.m

@@ -0,0 +1,60 @@
+//
+//  FLEXTableContentHeaderCell.m
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/26.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import "FLEXTableColumnHeader.h"
+
+@implementation FLEXTableColumnHeader
+{
+    UILabel *_arrowLabel;
+}
+
+
+- (instancetype)initWithFrame:(CGRect)frame
+{
+    self = [super initWithFrame:frame];
+    if (self) {
+        self.backgroundColor = [UIColor whiteColor];
+        
+        UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 0, frame.size.width - 25, frame.size.height)];
+        label.font = [UIFont systemFontOfSize:13.0];
+        [self addSubview:label];
+        self.label = label;
+        
+        
+        _arrowLabel = [[UILabel alloc] initWithFrame:CGRectMake(frame.size.width - 20, 0, 20, frame.size.height)];
+        _arrowLabel.font = [UIFont systemFontOfSize:13.0];
+        [self addSubview:_arrowLabel];
+        
+        UIView *line = [[UIView alloc] initWithFrame:CGRectMake(frame.size.width - 1, 2, 1, frame.size.height - 4)];
+        line.backgroundColor = [UIColor colorWithWhite:0.803 alpha:0.850];
+        [self addSubview:line];
+        
+    }
+    return self;
+}
+
+- (void)changeSortStatusWithType:(FLEXTableColumnHeaderSortType)type
+{
+    switch (type) {
+        case FLEXTableColumnHeaderSortTypeNone:
+            _arrowLabel.text = @"";
+            break;
+        case FLEXTableColumnHeaderSortTypeAsc:
+            _arrowLabel.text = @"⬆️";
+            break;
+        case FLEXTableColumnHeaderSortTypeDesc:
+            _arrowLabel.text = @"⬇️";
+            break;
+    }
+}
+
+
+
+
+
+@end

+ 27 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentCell.h

@@ -0,0 +1,27 @@
+//
+//  FLEXTableContentCell.h
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/24.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@class FLEXTableContentCell;
+@protocol FLEXTableContentCellDelegate <NSObject>
+
+@optional
+- (void)tableContentCell:(FLEXTableContentCell *)tableView labelDidTapWithText:(NSString *)text;
+
+@end
+
+@interface FLEXTableContentCell : UITableViewCell
+
+@property (nonatomic, strong)NSArray *labels;
+
+@property (nonatomic, weak) id<FLEXTableContentCellDelegate>delegate;
+
++ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
+
+@end

+ 66 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentCell.m

@@ -0,0 +1,66 @@
+//
+//  FLEXTableContentCell.m
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/24.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import "FLEXTableContentCell.h"
+#import "FLEXMultiColumnTableView.h"
+
+@interface FLEXTableContentCell ()
+
+@end
+
+@implementation FLEXTableContentCell
+
++ (instancetype)cellWithTableView:(UITableView *)tableView columnNumber:(NSInteger)number;
+{
+    static NSString *identifier = @"FLEXTableContentCell";
+    FLEXTableContentCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
+    if (!cell) {
+        cell = [[FLEXTableContentCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
+        NSMutableArray *labels = [NSMutableArray array];
+        for (int i = 0; i < number ; i++) {
+            UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];
+            label.backgroundColor = [UIColor whiteColor];
+            label.font            = [UIFont systemFontOfSize:13.0];
+            label.textAlignment   = NSTextAlignmentLeft;
+            label.backgroundColor = [UIColor greenColor];
+            [labels addObject:label];
+            
+            UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc] initWithTarget:cell
+                                                                                      action:@selector(labelDidTap:)];
+            [label addGestureRecognizer:gesture];
+            label.userInteractionEnabled = YES;
+            
+            [cell.contentView addSubview:label];
+            cell.contentView.backgroundColor = [UIColor whiteColor];
+        }
+        cell.labels = labels;
+    }
+    return cell;
+}
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+    CGFloat labelWidth  = self.contentView.frame.size.width / self.labels.count;
+    CGFloat labelHeight = self.contentView.frame.size.height;
+    for (int i = 0; i < self.labels.count; i++) {
+        UILabel *label = self.labels[i];
+        label.frame = CGRectMake(labelWidth * i + 5, 0, (labelWidth - 10), labelHeight);
+    }
+}
+
+
+- (void)labelDidTap:(UIGestureRecognizer *)gesture
+{
+    UILabel *label = (UILabel *)gesture.view;
+    if ([self.delegate respondsToSelector:@selector(tableContentCell:labelDidTapWithText:)]) {
+        [self.delegate tableContentCell:self labelDidTapWithText:label.text];
+    }
+}
+
+@end

+ 16 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.h

@@ -0,0 +1,16 @@
+//
+//  PTTableContentViewController.h
+//  PTDatabaseReader
+//
+//  Created by Peng Tao on 15/11/23.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXTableContentViewController : UIViewController
+
+@property (nonatomic, strong) NSArray *columnsArray;
+@property (nonatomic, strong) NSArray *contentsArray;
+
+@end

+ 184 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableContentViewController.m

@@ -0,0 +1,184 @@
+//
+//  PTTableContentViewController.m
+//  PTDatabaseReader
+//
+//  Created by Peng Tao on 15/11/23.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import "FLEXTableContentViewController.h"
+#import "FLEXMultiColumnTableView.h"
+#import "FLEXWebViewController.h"
+
+
+@interface FLEXTableContentViewController ()<FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate>
+
+@property (nonatomic, strong)FLEXMultiColumnTableView *multiColumView;
+
+@end
+
+@implementation FLEXTableContentViewController
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        
+        CGRect rectStatus = [UIApplication sharedApplication].statusBarFrame;
+        CGFloat y = 64;
+        if (rectStatus.size.height == 0) {
+            y = 32;
+        }
+        _multiColumView = [[FLEXMultiColumnTableView alloc] initWithFrame:
+                           CGRectMake(0, y, self.view.frame.size.width, self.view.frame.size.height - y)];
+        
+        _multiColumView.autoresizingMask          = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+        _multiColumView.backgroundColor           = [UIColor whiteColor];
+        _multiColumView.dataSource                = self;
+        _multiColumView.delegate                  = self;
+        self.automaticallyAdjustsScrollViewInsets = NO;
+        
+        
+        [self.view addSubview:_multiColumView];
+    }
+    return self;
+}
+
+- (void)viewWillAppear:(BOOL)animated
+{
+    [super viewWillAppear:animated];
+    [self.multiColumView reloadData];
+    
+}
+
+#pragma mark -
+#pragma mark MultiColumnTableView DataSource
+
+- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView
+{
+    return self.columnsArray.count;
+}
+- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView
+{
+    return self.contentsArray.count;
+}
+
+
+- (NSString *)columnNameInColumn:(NSInteger)column
+{
+    return self.columnsArray[column];
+}
+
+
+- (NSString *)rowNameInRow:(NSInteger)row
+{
+    return [NSString stringWithFormat:@"%ld",(long)row];
+}
+
+- (NSString *)contentAtColumn:(NSInteger)column row:(NSInteger)row
+{
+    if (self.contentsArray.count > row) {
+        NSDictionary *dic = self.contentsArray[row];
+        if (self.contentsArray.count > column) {
+            return [NSString stringWithFormat:@"%@",[dic objectForKey:self.columnsArray[column]]];
+        }
+    }
+    return @"";
+}
+
+- (NSArray *)contentAtRow:(NSInteger)row
+{
+    NSMutableArray *result = [NSMutableArray array];
+    if (self.contentsArray.count > row) {
+        NSDictionary *dic = self.contentsArray[row];
+        for (int i = 0; i < self.columnsArray.count; i ++) {
+            [result addObject:dic[self.columnsArray[i]]];
+        }
+        return  result;
+    }
+    return nil;
+}
+
+- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
+      heightForContentCellInRow:(NSInteger)row
+{
+    return 40;
+}
+
+- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
+    widthForContentCellInColumn:(NSInteger)column
+{
+    return 120;
+}
+
+- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView
+{
+    return 40;
+}
+
+- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView
+{
+    NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.contentsArray.count];
+    NSDictionary *attrs = @{@"NSFontAttributeName":[UIFont systemFontOfSize:17.0]};
+    CGSize size =   [str boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, 14)
+                                      options:NSStringDrawingUsesLineFragmentOrigin
+                                   attributes:attrs context:nil].size;
+    return size.width + 20;
+}
+
+#pragma mark -
+#pragma mark MultiColumnTableView Delegate
+
+
+- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapLabelWithText:(NSString *)text
+{
+    FLEXWebViewController * detailViewController = [[FLEXWebViewController alloc] initWithText:text];
+    [self.navigationController pushViewController:detailViewController animated:YES];
+}
+
+- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didTapHeaderWithText:(NSString *)text sortType:(FLEXTableColumnHeaderSortType)sortType
+{
+    
+    NSArray *sortContentData = [self.contentsArray sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
+        
+        if ([obj1 objectForKey:text] == [NSNull null]) {
+            return NSOrderedAscending;
+        }
+        if ([obj2 objectForKey:text] == [NSNull null]) {
+            return NSOrderedDescending;
+        }
+        NSComparisonResult result =  [[obj1 objectForKey:text] compare:[obj2 objectForKey:text]];
+        
+        return result;
+    }];
+    if (sortType == FLEXTableColumnHeaderSortTypeDesc) {
+        NSEnumerator *contentReverseEvumerator = [sortContentData reverseObjectEnumerator];
+        sortContentData = [NSArray arrayWithArray:[contentReverseEvumerator allObjects]];
+    }
+    
+    self.contentsArray = sortContentData;
+    [self.multiColumView reloadData];
+}
+
+#pragma mark -
+#pragma mark About Transition
+
+- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
+              withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator
+{
+    [super willTransitionToTraitCollection:newCollection
+                 withTransitionCoordinator:coordinator];
+    [coordinator animateAlongsideTransition:^(id <UIViewControllerTransitionCoordinatorContext> context) {
+        if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
+            
+            _multiColumView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
+        }
+        else {
+            _multiColumView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
+        }
+        [self.view setNeedsLayout];
+    } completion:nil];
+}
+
+
+@end

+ 17 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableLeftCell.h

@@ -0,0 +1,17 @@
+//
+//  FLEXTableLeftCell.h
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/24.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXTableLeftCell : UITableViewCell
+
+@property (nonatomic, strong) UILabel *titlelabel;
+
++ (instancetype)cellWithTableView:(UITableView *)tableView;
+
+@end

+ 35 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableLeftCell.m

@@ -0,0 +1,35 @@
+//
+//  FLEXTableLeftCell.m
+//  UICatalog
+//
+//  Created by Peng Tao on 15/11/24.
+//  Copyright © 2015年 f. All rights reserved.
+//
+
+#import "FLEXTableLeftCell.h"
+
+@implementation FLEXTableLeftCell
+
++ (instancetype)cellWithTableView:(UITableView *)tableView
+{
+    static NSString *identifier = @"FLEXTableLeftCell";
+    FLEXTableLeftCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
+    
+    if (!cell) {
+        cell = [[FLEXTableLeftCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
+        UILabel *textLabel               = [[UILabel alloc] initWithFrame:CGRectZero];
+        textLabel.textAlignment          = NSTextAlignmentCenter;
+        textLabel.font                   = [UIFont systemFontOfSize:13.0];
+        textLabel.backgroundColor = [UIColor clearColor];
+        [cell.contentView addSubview:textLabel];
+        cell.titlelabel = textLabel;
+    }
+    return cell;
+}
+
+- (void)layoutSubviews
+{
+    [super layoutSubviews];
+    self.titlelabel.frame = self.contentView.frame;
+}
+@end

+ 15 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableListViewController.h

@@ -0,0 +1,15 @@
+//
+//  PTTableListViewController.h
+//  PTDatabaseReader
+//
+//  Created by Peng Tao on 15/11/23.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface FLEXTableListViewController : UITableViewController
+
+- (instancetype)initWithPath:(NSString *)path;
+
+@end

+ 82 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/FLEXTableListViewController.m

@@ -0,0 +1,82 @@
+//
+//  PTTableListViewController.m
+//  PTDatabaseReader
+//
+//  Created by Peng Tao on 15/11/23.
+//  Copyright © 2015年 Peng Tao. All rights reserved.
+//
+
+#import "FLEXTableListViewController.h"
+#import "FLEXDatabaseManager.h"
+#import "FLEXTableContentViewController.h"
+
+@interface FLEXTableListViewController ()
+{
+    FLEXDatabaseManager *_dbm;
+    NSString *_databasePath;
+}
+
+@property (nonatomic, strong) NSArray *tables;
+
+@end
+
+@implementation FLEXTableListViewController
+
+
+- (instancetype)initWithPath:(NSString *)path
+{
+    self = [super initWithStyle:UITableViewStyleGrouped];
+    if (self) {
+        _databasePath = [path copy];
+        _dbm = [[FLEXDatabaseManager alloc] initWithPath:path];
+        [_dbm open];
+        [self getAllTables];
+    }
+    return self;
+}
+
+- (void)getAllTables
+{
+    NSArray *resultArray = [_dbm queryAllTables];
+    NSMutableArray *array = [NSMutableArray array];
+    for (NSDictionary *dict in resultArray) {
+        [array addObject:dict[@"name"]];
+    }
+    self.tables = array;
+}
+
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
+{
+    return self.tables.count;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"FLEXTableListViewControllerCell"];
+    if (!cell) {
+        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
+                                      reuseIdentifier:@"FLEXTableListViewControllerCell"];
+    }
+    cell.textLabel.text = self.tables[indexPath.row];
+    return cell;
+}
+
+
+- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
+{
+    FLEXTableContentViewController *contentViewController = [[FLEXTableContentViewController alloc] init];
+    
+    contentViewController.contentsArray = [_dbm queryAllDataWithTableName:self.tables[indexPath.row]];
+    contentViewController.columnsArray = [_dbm queryAllColumnsWithTableName:self.tables[indexPath.row]];
+    
+    contentViewController.title = self.tables[indexPath.row];
+    [self.navigationController pushViewController:contentViewController animated:YES];
+}
+
+
+- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
+{
+    return [NSString stringWithFormat:@"%lu tables", (unsigned long)self.tables.count];
+}
+
+@end

+ 21 - 0
Classes/GlobalStateExplorers/DatabaseBrowser/LICENSE

@@ -0,0 +1,21 @@
+
+FMDB
+Copyright (c) 2008-2014 Flying Meat Inc.
+
+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.

+ 5 - 1
Classes/GlobalStateExplorers/FLEXFileBrowserTableViewController.m

@@ -11,6 +11,7 @@
 #import "FLEXUtility.h"
 #import "FLEXWebViewController.h"
 #import "FLEXImagePreviewViewController.h"
+#import "FLEXTableListViewController.h"
 
 @interface FLEXFileBrowserTableViewCell : UITableViewCell
 @end
@@ -253,7 +254,10 @@
                 drillInViewController = [[FLEXWebViewController alloc] initWithText:prettyString];
             } else if ([FLEXWebViewController supportsPathExtension:[subpath pathExtension]]) {
                 drillInViewController = [[FLEXWebViewController alloc] initWithURL:[NSURL fileURLWithPath:fullPath]];
-            } else {
+            } else if ([[subpath pathExtension] isEqualToString:@"db"]) {
+              drillInViewController = [[FLEXTableListViewController alloc] initWithPath:fullPath];
+            }
+            else {
                 NSString *fileString = [NSString stringWithContentsOfFile:fullPath encoding:NSUTF8StringEncoding error:NULL];
                 if ([fileString length] > 0) {
                     drillInViewController = [[FLEXWebViewController alloc] initWithText:fileString];

+ 55 - 1
Example/UICatalog.xcodeproj/project.pbxproj

@@ -45,7 +45,15 @@
 		53874F9918F36B1800510922 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 53874F9718F36B1800510922 /* Localizable.strings */; };
 		650855EB1A9007D5006109A1 /* FLEXArgumentInputDateView.m in Sources */ = {isa = PBXBuildFile; fileRef = 650855EA1A9007D5006109A1 /* FLEXArgumentInputDateView.m */; };
 		65F8DC6C1A8F11020076F87B /* FLEXFileBrowserFileOperationController.m in Sources */ = {isa = PBXBuildFile; fileRef = 65F8DC6B1A8F11020076F87B /* FLEXFileBrowserFileOperationController.m */; };
-		679F6A121BD61B2400A8C94C /* FLEXCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F6A111BD61B2400A8C94C /* FLEXCookiesTableViewController.m */; settings = {ASSET_TAGS = (); }; };
+		679F6A121BD61B2400A8C94C /* FLEXCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F6A111BD61B2400A8C94C /* FLEXCookiesTableViewController.m */; };
+		779B1EED1C0C4EF6001F5E49 /* FLEXDatabaseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EE01C0C4EF6001F5E49 /* FLEXDatabaseManager.m */; };
+		779B1EEE1C0C4EF6001F5E49 /* FLEXMultiColumnTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EE21C0C4EF6001F5E49 /* FLEXMultiColumnTableView.m */; };
+		779B1EEF1C0C4EF6001F5E49 /* FLEXTableColumnHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EE41C0C4EF6001F5E49 /* FLEXTableColumnHeader.m */; };
+		779B1EF01C0C4EF6001F5E49 /* FLEXTableContentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EE61C0C4EF6001F5E49 /* FLEXTableContentCell.m */; };
+		779B1EF11C0C4EF6001F5E49 /* FLEXTableContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EE81C0C4EF6001F5E49 /* FLEXTableContentViewController.m */; };
+		779B1EF21C0C4EF6001F5E49 /* FLEXTableLeftCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EEA1C0C4EF6001F5E49 /* FLEXTableLeftCell.m */; };
+		779B1EF31C0C4EF6001F5E49 /* FLEXTableListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EEC1C0C4EF6001F5E49 /* FLEXTableListViewController.m */; };
+		779B1EF51C0C4F25001F5E49 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */; };
 		9421B88D1A8BBCB200BA3E46 /* FLEXNetworkHistoryTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9421B8801A8BBCB200BA3E46 /* FLEXNetworkHistoryTableViewController.m */; };
 		9421B88E1A8BBCB200BA3E46 /* FLEXNetworkRecorder.m in Sources */ = {isa = PBXBuildFile; fileRef = 9421B8821A8BBCB200BA3E46 /* FLEXNetworkRecorder.m */; };
 		9421B88F1A8BBCB200BA3E46 /* FLEXNetworkTransaction.m in Sources */ = {isa = PBXBuildFile; fileRef = 9421B8841A8BBCB200BA3E46 /* FLEXNetworkTransaction.m */; };
@@ -185,6 +193,21 @@
 		65F8DC6B1A8F11020076F87B /* FLEXFileBrowserFileOperationController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXFileBrowserFileOperationController.m; sourceTree = "<group>"; };
 		679F6A101BD61B2400A8C94C /* FLEXCookiesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXCookiesTableViewController.h; sourceTree = "<group>"; };
 		679F6A111BD61B2400A8C94C /* FLEXCookiesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXCookiesTableViewController.m; sourceTree = "<group>"; };
+		779B1EDF1C0C4EF6001F5E49 /* FLEXDatabaseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXDatabaseManager.h; sourceTree = "<group>"; };
+		779B1EE01C0C4EF6001F5E49 /* FLEXDatabaseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXDatabaseManager.m; sourceTree = "<group>"; };
+		779B1EE11C0C4EF6001F5E49 /* FLEXMultiColumnTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMultiColumnTableView.h; sourceTree = "<group>"; };
+		779B1EE21C0C4EF6001F5E49 /* FLEXMultiColumnTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMultiColumnTableView.m; sourceTree = "<group>"; };
+		779B1EE31C0C4EF6001F5E49 /* FLEXTableColumnHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableColumnHeader.h; sourceTree = "<group>"; };
+		779B1EE41C0C4EF6001F5E49 /* FLEXTableColumnHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableColumnHeader.m; sourceTree = "<group>"; };
+		779B1EE51C0C4EF6001F5E49 /* FLEXTableContentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableContentCell.h; sourceTree = "<group>"; };
+		779B1EE61C0C4EF6001F5E49 /* FLEXTableContentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableContentCell.m; sourceTree = "<group>"; };
+		779B1EE71C0C4EF6001F5E49 /* FLEXTableContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableContentViewController.h; sourceTree = "<group>"; };
+		779B1EE81C0C4EF6001F5E49 /* FLEXTableContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableContentViewController.m; sourceTree = "<group>"; };
+		779B1EE91C0C4EF6001F5E49 /* FLEXTableLeftCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableLeftCell.h; sourceTree = "<group>"; };
+		779B1EEA1C0C4EF6001F5E49 /* FLEXTableLeftCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableLeftCell.m; sourceTree = "<group>"; };
+		779B1EEB1C0C4EF6001F5E49 /* FLEXTableListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableListViewController.h; sourceTree = "<group>"; };
+		779B1EEC1C0C4EF6001F5E49 /* FLEXTableListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableListViewController.m; sourceTree = "<group>"; };
+		779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = ../../../../../../usr/lib/libsqlite3.dylib; sourceTree = "<group>"; };
 		9421B87F1A8BBCB200BA3E46 /* FLEXNetworkHistoryTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkHistoryTableViewController.h; sourceTree = "<group>"; };
 		9421B8801A8BBCB200BA3E46 /* FLEXNetworkHistoryTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXNetworkHistoryTableViewController.m; sourceTree = "<group>"; };
 		9421B8811A8BBCB200BA3E46 /* FLEXNetworkRecorder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXNetworkRecorder.h; sourceTree = "<group>"; };
@@ -319,6 +342,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				779B1EF51C0C4F25001F5E49 /* libsqlite3.dylib in Frameworks */,
 				94CB4D431A97183E0054A905 /* libz.dylib in Frameworks */,
 				5356824018F3656900BAAD62 /* CoreGraphics.framework in Frameworks */,
 				5356824218F3656900BAAD62 /* UIKit.framework in Frameworks */,
@@ -351,6 +375,7 @@
 		5356823C18F3656900BAAD62 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				779B1EF41C0C4F25001F5E49 /* libsqlite3.dylib */,
 				94CB4D421A97183E0054A905 /* libz.dylib */,
 				5356823D18F3656900BAAD62 /* Foundation.framework */,
 				5356823F18F3656900BAAD62 /* CoreGraphics.framework */,
@@ -456,6 +481,27 @@
 			name = Application;
 			sourceTree = "<group>";
 		};
+		779B1EDE1C0C4EF6001F5E49 /* DatabaseBrowser */ = {
+			isa = PBXGroup;
+			children = (
+				779B1EDF1C0C4EF6001F5E49 /* FLEXDatabaseManager.h */,
+				779B1EE01C0C4EF6001F5E49 /* FLEXDatabaseManager.m */,
+				779B1EE11C0C4EF6001F5E49 /* FLEXMultiColumnTableView.h */,
+				779B1EE21C0C4EF6001F5E49 /* FLEXMultiColumnTableView.m */,
+				779B1EE31C0C4EF6001F5E49 /* FLEXTableColumnHeader.h */,
+				779B1EE41C0C4EF6001F5E49 /* FLEXTableColumnHeader.m */,
+				779B1EE51C0C4EF6001F5E49 /* FLEXTableContentCell.h */,
+				779B1EE61C0C4EF6001F5E49 /* FLEXTableContentCell.m */,
+				779B1EE71C0C4EF6001F5E49 /* FLEXTableContentViewController.h */,
+				779B1EE81C0C4EF6001F5E49 /* FLEXTableContentViewController.m */,
+				779B1EE91C0C4EF6001F5E49 /* FLEXTableLeftCell.h */,
+				779B1EEA1C0C4EF6001F5E49 /* FLEXTableLeftCell.m */,
+				779B1EEB1C0C4EF6001F5E49 /* FLEXTableListViewController.h */,
+				779B1EEC1C0C4EF6001F5E49 /* FLEXTableListViewController.m */,
+			);
+			path = DatabaseBrowser;
+			sourceTree = "<group>";
+		};
 		9421B87E1A8BBCB200BA3E46 /* Network */ = {
 			isa = PBXGroup;
 			children = (
@@ -639,6 +685,7 @@
 		944F7473197B458C009AB039 /* Global State Explorers */ = {
 			isa = PBXGroup;
 			children = (
+				779B1EDE1C0C4EF6001F5E49 /* DatabaseBrowser */,
 				946C6EC61A75986C006545C2 /* System Log */,
 				944F7474197B458C009AB039 /* FLEXClassesTableViewController.h */,
 				944F7475197B458C009AB039 /* FLEXClassesTableViewController.m */,
@@ -779,6 +826,7 @@
 			isa = PBXSourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				779B1EEF1C0C4EF6001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				944F749B197B458C009AB039 /* FLEXArgumentInputNotSupportedView.m in Sources */,
 				535682AE18F3670300BAAD62 /* AAPLDatePickerController.m in Sources */,
 				946C6ECC1A759928006545C2 /* FLEXSystemLogTableViewController.m in Sources */,
@@ -804,6 +852,7 @@
 				944F74B5197B458C009AB039 /* FLEXHierarchyTableViewCell.m in Sources */,
 				944F74AD197B458C009AB039 /* FLEXWindow.m in Sources */,
 				944F749D197B458C009AB039 /* FLEXArgumentInputStringView.m in Sources */,
+				779B1EF11C0C4EF6001F5E49 /* FLEXTableContentViewController.m in Sources */,
 				944F74A9197B458C009AB039 /* FLEXExplorerToolbar.m in Sources */,
 				944F74A8197B458C009AB039 /* FLEXPropertyEditorViewController.m in Sources */,
 				944F74B0197B458C009AB039 /* FLEXGlobalsTableViewController.m in Sources */,
@@ -820,6 +869,7 @@
 				944F74A5197B458C009AB039 /* FLEXFieldEditorViewController.m in Sources */,
 				944F7489197B458C009AB039 /* FLEXArrayExplorerViewController.m in Sources */,
 				535682B418F3670300BAAD62 /* AAPLPickerViewController.m in Sources */,
+				779B1EF01C0C4EF6001F5E49 /* FLEXTableContentCell.m in Sources */,
 				946C6EC91A7598D3006545C2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				535682BE18F3670300BAAD62 /* AAPLWebViewController.m in Sources */,
 				944F7490197B458C009AB039 /* FLEXObjectExplorerViewController.m in Sources */,
@@ -853,16 +903,20 @@
 				535682AD18F3670300BAAD62 /* AAPLCustomToolbarViewController.m in Sources */,
 				944F748F197B458C009AB039 /* FLEXObjectExplorerFactory.m in Sources */,
 				944F748D197B458C009AB039 /* FLEXDictionaryExplorerViewController.m in Sources */,
+				779B1EEE1C0C4EF6001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
 				535682A818F3670300BAAD62 /* AAPLActivityIndicatorViewController.m in Sources */,
 				944F74A4197B458C009AB039 /* FLEXFieldEditorView.m in Sources */,
 				944F74A7197B458C009AB039 /* FLEXMethodCallingViewController.m in Sources */,
+				779B1EED1C0C4EF6001F5E49 /* FLEXDatabaseManager.m in Sources */,
 				535682B818F3670300BAAD62 /* AAPLSplitViewControllerDelegate.m in Sources */,
+				779B1EF21C0C4EF6001F5E49 /* FLEXTableLeftCell.m in Sources */,
 				D03647D919847248007D2A1B /* FLEXGlobalsTableViewControllerEntry.m in Sources */,
 				535682AC18F3670300BAAD62 /* AAPLCustomSearchBarViewController.m in Sources */,
 				535682A718F3670300BAAD62 /* AAPLActionSheetViewController.m in Sources */,
 				01985ABC1984DE9500A65332 /* FLEXArgumentInputFontsPickerView.m in Sources */,
 				944F74AA197B458C009AB039 /* FLEXExplorerViewController.m in Sources */,
 				944F7492197B458C009AB039 /* FLEXViewControllerExplorerViewController.m in Sources */,
+				779B1EF31C0C4EF6001F5E49 /* FLEXTableListViewController.m in Sources */,
 				5356824A18F3656900BAAD62 /* main.m in Sources */,
 				679F6A121BD61B2400A8C94C /* FLEXCookiesTableViewController.m in Sources */,
 				535682B118F3670300BAAD62 /* AAPLImageViewController.m in Sources */,

+ 74 - 2
FLEX.xcodeproj/project.pbxproj

@@ -7,6 +7,7 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		04F1CA191C137CF1000A52B0 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 04F1CA181C137CF1000A52B0 /* LICENSE */; };
 		3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94241B5B20570088C3F2 /* FLEX.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		3A4C94C51B5B21410088C3F2 /* FLEXArrayExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C943C1B5B21410088C3F2 /* FLEXArrayExplorerViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C943D1B5B21410088C3F2 /* FLEXArrayExplorerViewController.m */; };
@@ -136,8 +137,23 @@
 		3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 3A4C94C21B5B21410088C3F2 /* FLEXNetworkObserver.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		3A4C95431B5B21410088C3F2 /* FLEXNetworkObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 3A4C94C31B5B21410088C3F2 /* FLEXNetworkObserver.m */; };
 		3A4C95471B5B217D0088C3F2 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A4C95461B5B217D0088C3F2 /* libz.dylib */; };
-		679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */; settings = {ASSET_TAGS = (); }; };
-		679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */; settings = {ASSET_TAGS = (); }; };
+		679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */; };
+		679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */; };
+		779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1EC01C0C4D7C001F5E49 /* FLEXDatabaseManager.h */; };
+		779B1ECF1C0C4D7C001F5E49 /* FLEXDatabaseManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EC11C0C4D7C001F5E49 /* FLEXDatabaseManager.m */; };
+		779B1ED01C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1EC21C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h */; };
+		779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EC31C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m */; };
+		779B1ED21C0C4D7C001F5E49 /* FLEXTableColumnHeader.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1EC41C0C4D7C001F5E49 /* FLEXTableColumnHeader.h */; };
+		779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EC51C0C4D7C001F5E49 /* FLEXTableColumnHeader.m */; };
+		779B1ED41C0C4D7C001F5E49 /* FLEXTableContentCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1EC61C0C4D7C001F5E49 /* FLEXTableContentCell.h */; };
+		779B1ED51C0C4D7C001F5E49 /* FLEXTableContentCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EC71C0C4D7C001F5E49 /* FLEXTableContentCell.m */; };
+		779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1EC81C0C4D7C001F5E49 /* FLEXTableContentViewController.h */; };
+		779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1EC91C0C4D7C001F5E49 /* FLEXTableContentViewController.m */; };
+		779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1ECA1C0C4D7C001F5E49 /* FLEXTableLeftCell.h */; };
+		779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1ECB1C0C4D7C001F5E49 /* FLEXTableLeftCell.m */; };
+		779B1EDA1C0C4D7C001F5E49 /* FLEXTableListViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 779B1ECC1C0C4D7C001F5E49 /* FLEXTableListViewController.h */; };
+		779B1EDB1C0C4D7C001F5E49 /* FLEXTableListViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 779B1ECD1C0C4D7C001F5E49 /* FLEXTableListViewController.m */; };
+		779B1EDD1C0C4EAD001F5E49 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 779B1EDC1C0C4EAD001F5E49 /* libsqlite3.dylib */; };
 		942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */; };
 		94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */; };
@@ -145,6 +161,7 @@
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
+		04F1CA181C137CF1000A52B0 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
 		3A4C941F1B5B20570088C3F2 /* FLEX.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FLEX.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		3A4C94231B5B20570088C3F2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
 		3A4C94241B5B20570088C3F2 /* FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEX.h; sourceTree = "<group>"; };
@@ -279,6 +296,21 @@
 		3A4C95461B5B217D0088C3F2 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
 		679F64841BD53B7B00A8C94C /* FLEXCookiesTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXCookiesTableViewController.h; sourceTree = "<group>"; };
 		679F64851BD53B7B00A8C94C /* FLEXCookiesTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXCookiesTableViewController.m; sourceTree = "<group>"; };
+		779B1EC01C0C4D7C001F5E49 /* FLEXDatabaseManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXDatabaseManager.h; sourceTree = "<group>"; };
+		779B1EC11C0C4D7C001F5E49 /* FLEXDatabaseManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXDatabaseManager.m; sourceTree = "<group>"; };
+		779B1EC21C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMultiColumnTableView.h; sourceTree = "<group>"; };
+		779B1EC31C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMultiColumnTableView.m; sourceTree = "<group>"; };
+		779B1EC41C0C4D7C001F5E49 /* FLEXTableColumnHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableColumnHeader.h; sourceTree = "<group>"; };
+		779B1EC51C0C4D7C001F5E49 /* FLEXTableColumnHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableColumnHeader.m; sourceTree = "<group>"; };
+		779B1EC61C0C4D7C001F5E49 /* FLEXTableContentCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableContentCell.h; sourceTree = "<group>"; };
+		779B1EC71C0C4D7C001F5E49 /* FLEXTableContentCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableContentCell.m; sourceTree = "<group>"; };
+		779B1EC81C0C4D7C001F5E49 /* FLEXTableContentViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableContentViewController.h; sourceTree = "<group>"; };
+		779B1EC91C0C4D7C001F5E49 /* FLEXTableContentViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableContentViewController.m; sourceTree = "<group>"; };
+		779B1ECA1C0C4D7C001F5E49 /* FLEXTableLeftCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableLeftCell.h; sourceTree = "<group>"; };
+		779B1ECB1C0C4D7C001F5E49 /* FLEXTableLeftCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableLeftCell.m; sourceTree = "<group>"; };
+		779B1ECC1C0C4D7C001F5E49 /* FLEXTableListViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXTableListViewController.h; sourceTree = "<group>"; };
+		779B1ECD1C0C4D7C001F5E49 /* FLEXTableListViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableListViewController.m; sourceTree = "<group>"; };
+		779B1EDC1C0C4EAD001F5E49 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = ../../../../../usr/lib/libsqlite3.dylib; sourceTree = "<group>"; };
 		942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardShortcutManager.h; sourceTree = "<group>"; };
 		942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXKeyboardShortcutManager.m; sourceTree = "<group>"; };
 		94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXKeyboardHelpViewController.h; sourceTree = "<group>"; };
@@ -290,6 +322,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				779B1EDD1C0C4EAD001F5E49 /* libsqlite3.dylib in Frameworks */,
 				3A4C95471B5B217D0088C3F2 /* libz.dylib in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -478,6 +511,7 @@
 		3A4C949A1B5B21410088C3F2 /* GlobalStateExplorers */ = {
 			isa = PBXGroup;
 			children = (
+				779B1EBF1C0C4D7C001F5E49 /* DatabaseBrowser */,
 				3A4C949B1B5B21410088C3F2 /* FLEXClassesTableViewController.h */,
 				3A4C949C1B5B21410088C3F2 /* FLEXClassesTableViewController.m */,
 				3A4C949D1B5B21410088C3F2 /* FLEXFileBrowserFileOperationController.h */,
@@ -549,11 +583,34 @@
 		3A4C95451B5B216C0088C3F2 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				779B1EDC1C0C4EAD001F5E49 /* libsqlite3.dylib */,
 				3A4C95461B5B217D0088C3F2 /* libz.dylib */,
 			);
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		779B1EBF1C0C4D7C001F5E49 /* DatabaseBrowser */ = {
+			isa = PBXGroup;
+			children = (
+				779B1EC01C0C4D7C001F5E49 /* FLEXDatabaseManager.h */,
+				779B1EC11C0C4D7C001F5E49 /* FLEXDatabaseManager.m */,
+				779B1EC21C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h */,
+				779B1EC31C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m */,
+				779B1EC41C0C4D7C001F5E49 /* FLEXTableColumnHeader.h */,
+				779B1EC51C0C4D7C001F5E49 /* FLEXTableColumnHeader.m */,
+				779B1EC61C0C4D7C001F5E49 /* FLEXTableContentCell.h */,
+				779B1EC71C0C4D7C001F5E49 /* FLEXTableContentCell.m */,
+				779B1EC81C0C4D7C001F5E49 /* FLEXTableContentViewController.h */,
+				779B1EC91C0C4D7C001F5E49 /* FLEXTableContentViewController.m */,
+				779B1ECA1C0C4D7C001F5E49 /* FLEXTableLeftCell.h */,
+				779B1ECB1C0C4D7C001F5E49 /* FLEXTableLeftCell.m */,
+				779B1ECC1C0C4D7C001F5E49 /* FLEXTableListViewController.h */,
+				779B1ECD1C0C4D7C001F5E49 /* FLEXTableListViewController.m */,
+				04F1CA181C137CF1000A52B0 /* LICENSE */,
+			);
+			path = DatabaseBrowser;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXHeadersBuildPhase section */
@@ -561,12 +618,14 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				779B1EDA1C0C4D7C001F5E49 /* FLEXTableListViewController.h in Headers */,
 				3A4C94ED1B5B21410088C3F2 /* FLEXArgumentInputColorView.h in Headers */,
 				3A4C94EB1B5B21410088C3F2 /* FLEXImagePreviewViewController.h in Headers */,
 				3A4C95381B5B21410088C3F2 /* FLEXNetworkRecorder.h in Headers */,
 				3A4C94251B5B20570088C3F2 /* FLEX.h in Headers */,
 				3A4C95051B5B21410088C3F2 /* FLEXArgumentInputViewFactory.h in Headers */,
 				3A4C951E1B5B21410088C3F2 /* FLEXClassesTableViewController.h in Headers */,
+				779B1ED21C0C4D7C001F5E49 /* FLEXTableColumnHeader.h in Headers */,
 				3A4C94FD1B5B21410088C3F2 /* FLEXArgumentInputStructView.h in Headers */,
 				3A4C95201B5B21410088C3F2 /* FLEXFileBrowserFileOperationController.h in Headers */,
 				3A4C953E1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.h in Headers */,
@@ -580,6 +639,7 @@
 				3A4C94DF1B5B21410088C3F2 /* FLEXMultilineTableViewCell.h in Headers */,
 				3A4C953A1B5B21410088C3F2 /* FLEXNetworkSettingsTableViewController.h in Headers */,
 				3A4C94D11B5B21410088C3F2 /* FLEXLayerExplorerViewController.h in Headers */,
+				779B1ED01C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h in Headers */,
 				3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */,
 				3A4C94E31B5B21410088C3F2 /* FLEXRuntimeUtility.h in Headers */,
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
@@ -591,6 +651,7 @@
 				3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */,
 				3A4C94F51B5B21410088C3F2 /* FLEXArgumentInputJSONObjectView.h in Headers */,
 				3A4C94F11B5B21410088C3F2 /* FLEXArgumentInputFontsPickerView.h in Headers */,
+				779B1ED61C0C4D7C001F5E49 /* FLEXTableContentViewController.h in Headers */,
 				3A4C94C91B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.h in Headers */,
 				3A4C95221B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.h in Headers */,
 				3A4C94FF1B5B21410088C3F2 /* FLEXArgumentInputSwitchView.h in Headers */,
@@ -601,9 +662,11 @@
 				3A4C94C51B5B21410088C3F2 /* FLEXArrayExplorerViewController.h in Headers */,
 				3A4C94CB1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.h in Headers */,
 				3A4C95071B5B21410088C3F2 /* FLEXDefaultEditorViewController.h in Headers */,
+				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
 				3A4C95011B5B21410088C3F2 /* FLEXArgumentInputTextView.h in Headers */,
 				3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */,
+				779B1ED41C0C4D7C001F5E49 /* FLEXTableContentCell.h in Headers */,
 				3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h in Headers */,
 				3A4C94EF1B5B21410088C3F2 /* FLEXArgumentInputDateView.h in Headers */,
 				3A4C94C71B5B21410088C3F2 /* FLEXClassExplorerViewController.h in Headers */,
@@ -629,6 +692,7 @@
 				94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
 				94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */,
 				3A4C94E11B5B21410088C3F2 /* FLEXResources.h in Headers */,
+				779B1ED81C0C4D7C001F5E49 /* FLEXTableLeftCell.h in Headers */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -690,6 +754,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				04F1CA191C137CF1000A52B0 /* LICENSE in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -712,6 +777,7 @@
 				3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
 				3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */,
 				3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
+				779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
 				3A4C94EE1B5B21410088C3F2 /* FLEXArgumentInputColorView.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
@@ -722,6 +788,7 @@
 				3A4C94E01B5B21410088C3F2 /* FLEXMultilineTableViewCell.m in Sources */,
 				3A4C95431B5B21410088C3F2 /* FLEXNetworkObserver.m in Sources */,
 				3A4C94D21B5B21410088C3F2 /* FLEXLayerExplorerViewController.m in Sources */,
+				779B1EDB1C0C4D7C001F5E49 /* FLEXTableListViewController.m in Sources */,
 				3A4C94E41B5B21410088C3F2 /* FLEXRuntimeUtility.m in Sources */,
 				3A4C94D41B5B21410088C3F2 /* FLEXObjectExplorerFactory.m in Sources */,
 				3A4C952F1B5B21410088C3F2 /* FLEXWebViewController.m in Sources */,
@@ -734,6 +801,7 @@
 				3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
 				3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
 				3A4C94DA1B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.m in Sources */,
+				779B1ED51C0C4D7C001F5E49 /* FLEXTableContentCell.m in Sources */,
 				3A4C94E21B5B21410088C3F2 /* FLEXResources.m in Sources */,
 				3A4C94D81B5B21410088C3F2 /* FLEXSetExplorerViewController.m in Sources */,
 				3A4C95311B5B21410088C3F2 /* FLEXSystemLogMessage.m in Sources */,
@@ -744,14 +812,18 @@
 				3A4C95351B5B21410088C3F2 /* FLEXSystemLogTableViewController.m in Sources */,
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
 				3A4C95161B5B21410088C3F2 /* FLEXExplorerViewController.m in Sources */,
+				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
 				3A4C95331B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				3A4C95191B5B21410088C3F2 /* FLEXManager.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
 				3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
+				779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
 				3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
 				3A4C94CC1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.m in Sources */,
 				3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */,
+				779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */,
+				779B1ECF1C0C4D7C001F5E49 /* FLEXDatabaseManager.m in Sources */,
 				3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
 				3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */,
 				3A4C950C1B5B21410088C3F2 /* FLEXFieldEditorViewController.m in Sources */,

+ 2 - 0
README.md

@@ -142,6 +142,8 @@ FLEX builds on ideas and inspiration from open source tools that came before it.
 - [Gist](https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396) from [@samdmarshall](https://github.com/samdmarshall): another example of enumerating malloc blocks.
 - [Non-pointer isa](http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html): an explanation of changes to the isa field on iOS for ARM64 and mention of the useful `objc_debug_isa_class_mask` variable.
 - [GZIP](https://github.com/nicklockwood/GZIP): A library for compressing/decompressing data on iOS using libz.
+- [FMDB](https://github.com/ccgus/fmdb): This is an Objective-C wrapper around SQLite
+