mirror of
https://github.com/SoPat712/YTLitePlus.git
synced 2025-12-27 00:37:21 -05:00
added files via upload
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// FLEXDBQueryRowCell.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@class FLEXDBQueryRowCell;
|
||||
|
||||
extern NSString * const kFLEXDBQueryRowCellReuse;
|
||||
|
||||
@protocol FLEXDBQueryRowCellLayoutSource <NSObject>
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column;
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column;
|
||||
|
||||
@end
|
||||
|
||||
@interface FLEXDBQueryRowCell : UITableViewCell
|
||||
|
||||
/// An array of NSString, NSNumber, or NSData objects
|
||||
@property (nonatomic) NSArray *data;
|
||||
@property (nonatomic, weak) id<FLEXDBQueryRowCellLayoutSource> layoutSource;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,75 @@
|
||||
//
|
||||
// FLEXDBQueryRowCell.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXDBQueryRowCell.h"
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "FLEXColor.h"
|
||||
|
||||
NSString * const kFLEXDBQueryRowCellReuse = @"kFLEXDBQueryRowCellReuse";
|
||||
|
||||
@interface FLEXDBQueryRowCell ()
|
||||
@property (nonatomic) NSInteger columnCount;
|
||||
@property (nonatomic) NSArray<UILabel *> *labels;
|
||||
@end
|
||||
|
||||
@implementation FLEXDBQueryRowCell
|
||||
|
||||
- (void)setData:(NSArray *)data {
|
||||
_data = data;
|
||||
self.columnCount = data.count;
|
||||
|
||||
[self.labels flex_forEach:^(UILabel *label, NSUInteger idx) {
|
||||
id content = self.data[idx];
|
||||
|
||||
if ([content isKindOfClass:[NSString class]]) {
|
||||
label.text = content;
|
||||
} else if (content == NSNull.null) {
|
||||
label.text = @"<null>";
|
||||
label.textColor = FLEXColor.deemphasizedTextColor;
|
||||
} else {
|
||||
label.text = [content description];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)setColumnCount:(NSInteger)columnCount {
|
||||
if (columnCount != _columnCount) {
|
||||
_columnCount = columnCount;
|
||||
|
||||
// Remove existing labels
|
||||
for (UILabel *l in self.labels) {
|
||||
[l removeFromSuperview];
|
||||
}
|
||||
|
||||
// Create new labels
|
||||
self.labels = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
|
||||
UILabel *label = [UILabel new];
|
||||
label.font = UIFont.flex_defaultTableCellFont;
|
||||
label.textAlignment = NSTextAlignmentLeft;
|
||||
[self.contentView addSubview:label];
|
||||
|
||||
return label;
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGFloat height = self.contentView.frame.size.height;
|
||||
|
||||
[self.labels flex_forEach:^(UILabel *label, NSUInteger i) {
|
||||
CGFloat width = [self.layoutSource dbQueryRowCell:self widthForColumn:i];
|
||||
CGFloat minX = [self.layoutSource dbQueryRowCell:self minXForColumn:i];
|
||||
label.frame = CGRectMake(minX + 5, 0, (width - 10), height);
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// 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>
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
/// Conformers should automatically open and close the database
|
||||
@protocol FLEXDatabaseManager <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
/// @return \c nil if the database couldn't be opened
|
||||
+ (instancetype)managerForDatabase:(NSString *)path;
|
||||
|
||||
/// @return a list of all table names
|
||||
- (NSArray<NSString *> *)queryAllTables;
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName;
|
||||
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName;
|
||||
|
||||
@optional
|
||||
|
||||
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName;
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)SQLStatement;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// 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 didSelectRow:(NSInteger)row;
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didSelectHeaderForColumn:(NSInteger)column sortType:(FLEXTableColumnHeaderSortType)sortType;
|
||||
|
||||
@end
|
||||
|
||||
@protocol FLEXMultiColumnTableViewDataSource <NSObject>
|
||||
|
||||
@required
|
||||
|
||||
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView;
|
||||
- (NSString *)columnTitle:(NSInteger)column;
|
||||
- (NSString *)rowTitle:(NSInteger)row;
|
||||
- (NSArray<NSString *> *)contentForRow:(NSInteger)row;
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView minWidthForContentCellInColumn:(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
|
||||
@@ -0,0 +1,339 @@
|
||||
//
|
||||
// PTMultiColumnTableView.m
|
||||
// PTMultiColumnTableViewDemo
|
||||
//
|
||||
// Created by Peng Tao on 15/11/16.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXDBQueryRowCell.h"
|
||||
#import "FLEXTableLeftCell.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXColor.h"
|
||||
|
||||
@interface FLEXMultiColumnTableView () <
|
||||
UITableViewDataSource, UITableViewDelegate,
|
||||
UIScrollViewDelegate, FLEXDBQueryRowCellLayoutSource
|
||||
>
|
||||
|
||||
@property (nonatomic) UIScrollView *contentScrollView;
|
||||
@property (nonatomic) UIScrollView *headerScrollView;
|
||||
@property (nonatomic) UITableView *leftTableView;
|
||||
@property (nonatomic) UITableView *contentTableView;
|
||||
@property (nonatomic) UIView *leftHeader;
|
||||
|
||||
@property (nonatomic) NSArray<UIView *> *headerViews;
|
||||
|
||||
/// \c NSNotFound if no column selected
|
||||
@property (nonatomic) NSInteger sortColumn;
|
||||
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
|
||||
|
||||
@property (nonatomic, readonly) NSInteger numberOfColumns;
|
||||
@property (nonatomic, readonly) NSInteger numberOfRows;
|
||||
@property (nonatomic, readonly) CGFloat topHeaderHeight;
|
||||
@property (nonatomic, readonly) CGFloat leftHeaderWidth;
|
||||
@property (nonatomic, readonly) CGFloat columnMargin;
|
||||
|
||||
@end
|
||||
|
||||
static const CGFloat kColumnMargin = 1;
|
||||
|
||||
@implementation FLEXMultiColumnTableView
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.autoresizingMask |= UIViewAutoresizingFlexibleWidth;
|
||||
self.autoresizingMask |= UIViewAutoresizingFlexibleHeight;
|
||||
self.autoresizingMask |= UIViewAutoresizingFlexibleTopMargin;
|
||||
self.backgroundColor = FLEXColor.groupedBackgroundColor;
|
||||
|
||||
[self loadHeaderScrollView];
|
||||
[self loadContentScrollView];
|
||||
[self loadLeftView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (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 topInsets = 0.f;
|
||||
|
||||
if (@available (iOS 11.0, *)) {
|
||||
topInsets = self.safeAreaInsets.top;
|
||||
}
|
||||
|
||||
CGFloat contentWidth = 0.0;
|
||||
NSInteger columnsCount = self.numberOfColumns;
|
||||
for (int i = 0; i < columnsCount; i++) {
|
||||
contentWidth += CGRectGetWidth(self.headerViews[i].bounds);
|
||||
}
|
||||
|
||||
CGFloat contentHeight = height - topheaderHeight - topInsets;
|
||||
|
||||
self.leftHeader.frame = CGRectMake(0, topInsets, self.leftHeaderWidth, self.topHeaderHeight);
|
||||
self.leftTableView.frame = CGRectMake(
|
||||
0, topheaderHeight + topInsets, leftHeaderWidth, contentHeight
|
||||
);
|
||||
self.headerScrollView.frame = CGRectMake(
|
||||
leftHeaderWidth, topInsets, 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 , contentHeight
|
||||
);
|
||||
self.contentScrollView.frame = CGRectMake(
|
||||
leftHeaderWidth, topheaderHeight + topInsets, width - leftHeaderWidth, contentHeight
|
||||
);
|
||||
self.contentScrollView.contentSize = self.contentTableView.frame.size;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - UI
|
||||
|
||||
- (void)loadHeaderScrollView {
|
||||
UIScrollView *headerScrollView = [UIScrollView new];
|
||||
headerScrollView.delegate = self;
|
||||
headerScrollView.backgroundColor = FLEXColor.secondaryGroupedBackgroundColor;
|
||||
self.headerScrollView = headerScrollView;
|
||||
|
||||
[self addSubview:headerScrollView];
|
||||
}
|
||||
|
||||
- (void)loadContentScrollView {
|
||||
UIScrollView *scrollView = [UIScrollView new];
|
||||
scrollView.bounces = NO;
|
||||
scrollView.delegate = self;
|
||||
|
||||
UITableView *tableView = [UITableView new];
|
||||
tableView.delegate = self;
|
||||
tableView.dataSource = self;
|
||||
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
[tableView registerClass:[FLEXDBQueryRowCell class]
|
||||
forCellReuseIdentifier:kFLEXDBQueryRowCellReuse
|
||||
];
|
||||
|
||||
[scrollView addSubview:tableView];
|
||||
[self addSubview:scrollView];
|
||||
|
||||
self.contentScrollView = scrollView;
|
||||
self.contentTableView = tableView;
|
||||
}
|
||||
|
||||
- (void)loadLeftView {
|
||||
UITableView *leftTableView = [UITableView new];
|
||||
leftTableView.delegate = self;
|
||||
leftTableView.dataSource = self;
|
||||
leftTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
|
||||
self.leftTableView = leftTableView;
|
||||
[self addSubview:leftTableView];
|
||||
|
||||
UIView *leftHeader = [UIView new];
|
||||
leftHeader.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
self.leftHeader = leftHeader;
|
||||
[self addSubview:leftHeader];
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Data
|
||||
|
||||
- (void)reloadData {
|
||||
[self loadHeaderData];
|
||||
[self loadLeftViewData];
|
||||
[self loadContentData];
|
||||
}
|
||||
|
||||
- (void)loadHeaderData {
|
||||
// Remove existing headers, if any
|
||||
for (UIView *subview in self.headerViews) {
|
||||
[subview removeFromSuperview];
|
||||
}
|
||||
|
||||
__block CGFloat xOffset = 0;
|
||||
|
||||
self.headerViews = [NSArray flex_forEachUpTo:self.numberOfColumns map:^id(NSUInteger column) {
|
||||
FLEXTableColumnHeader *header = [FLEXTableColumnHeader new];
|
||||
header.titleLabel.text = [self columnTitle:column];
|
||||
|
||||
CGSize fittingSize = CGSizeMake(CGFLOAT_MAX, self.topHeaderHeight - 1);
|
||||
CGFloat width = self.columnMargin + MAX(
|
||||
[self minContentWidthForColumn:column],
|
||||
[header sizeThatFits:fittingSize].width
|
||||
);
|
||||
header.frame = CGRectMake(xOffset, 0, width, self.topHeaderHeight - 1);
|
||||
|
||||
if (column == self.sortColumn) {
|
||||
header.sortType = self.sortType;
|
||||
}
|
||||
|
||||
// Header tap gesture
|
||||
UITapGestureRecognizer *gesture = [[UITapGestureRecognizer alloc]
|
||||
initWithTarget:self action:@selector(contentHeaderTap:)
|
||||
];
|
||||
[header addGestureRecognizer:gesture];
|
||||
header.userInteractionEnabled = YES;
|
||||
|
||||
xOffset += width;
|
||||
[self.headerScrollView addSubview:header];
|
||||
return header;
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)contentHeaderTap:(UIGestureRecognizer *)gesture {
|
||||
NSInteger newSortColumn = [self.headerViews indexOfObject:gesture.view];
|
||||
FLEXTableColumnHeaderSortType newType = FLEXNextTableColumnHeaderSortType(self.sortType);
|
||||
|
||||
// Reset old header
|
||||
FLEXTableColumnHeader *oldHeader = (id)self.headerViews[self.sortColumn];
|
||||
oldHeader.sortType = FLEXTableColumnHeaderSortTypeNone;
|
||||
|
||||
// Update new header
|
||||
FLEXTableColumnHeader *newHeader = (id)self.headerViews[newSortColumn];
|
||||
newHeader.sortType = newType;
|
||||
|
||||
// Update self
|
||||
self.sortColumn = newSortColumn;
|
||||
self.sortType = newType;
|
||||
|
||||
// Notify delegate
|
||||
[self.delegate multiColumnTableView:self didSelectHeaderForColumn:newSortColumn sortType:newType];
|
||||
}
|
||||
|
||||
- (void)loadContentData {
|
||||
[self.contentTableView reloadData];
|
||||
}
|
||||
|
||||
- (void)loadLeftViewData {
|
||||
[self.leftTableView reloadData];
|
||||
}
|
||||
|
||||
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
|
||||
// Alternating background color
|
||||
UIColor *backgroundColor = FLEXColor.primaryBackgroundColor;
|
||||
if (indexPath.row % 2 != 0) {
|
||||
backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
}
|
||||
|
||||
// Left side table view for row numbers
|
||||
if (tableView == self.leftTableView) {
|
||||
FLEXTableLeftCell *cell = [FLEXTableLeftCell cellWithTableView:tableView];
|
||||
cell.contentView.backgroundColor = backgroundColor;
|
||||
cell.titlelabel.text = [self rowTitle:indexPath.row];
|
||||
return cell;
|
||||
}
|
||||
// Right side table view for data
|
||||
else {
|
||||
FLEXDBQueryRowCell *cell = [tableView
|
||||
dequeueReusableCellWithIdentifier:kFLEXDBQueryRowCellReuse forIndexPath:indexPath
|
||||
];
|
||||
|
||||
cell.contentView.backgroundColor = backgroundColor;
|
||||
cell.data = [self.dataSource contentForRow:indexPath.row];
|
||||
cell.layoutSource = self;
|
||||
NSAssert(cell.data.count == self.numberOfColumns, @"Count of data provided was incorrect");
|
||||
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];
|
||||
}
|
||||
|
||||
// Scroll all scroll views in sync
|
||||
- (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 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.delegate multiColumnTableView:self didSelectRow:indexPath.row];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#pragma mark FLEXDBQueryRowCellLayoutSource
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell minXForColumn:(NSUInteger)column {
|
||||
return CGRectGetMinX(self.headerViews[column].frame);
|
||||
}
|
||||
|
||||
- (CGFloat)dbQueryRowCell:(FLEXDBQueryRowCell *)dbQueryRowCell widthForColumn:(NSUInteger)column {
|
||||
return CGRectGetWidth(self.headerViews[column].bounds);
|
||||
}
|
||||
|
||||
|
||||
#pragma mark DataSource Accessor
|
||||
|
||||
- (NSInteger)numberOfRows {
|
||||
return [self.dataSource numberOfRowsInTableView:self];
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfColumns {
|
||||
return [self.dataSource numberOfColumnsInTableView:self];
|
||||
}
|
||||
|
||||
- (NSString *)columnTitle:(NSInteger)column {
|
||||
return [self.dataSource columnTitle:column];
|
||||
}
|
||||
|
||||
- (NSString *)rowTitle:(NSInteger)row {
|
||||
return [self.dataSource rowTitle:row];
|
||||
}
|
||||
|
||||
- (CGFloat)minContentWidthForColumn:(NSInteger)column {
|
||||
return [self.dataSource multiColumnTableView:self minWidthForContentCellInColumn: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;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// FLEXRealmDatabaseManager.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 28/01/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
|
||||
@interface FLEXRealmDatabaseManager : NSObject <FLEXDatabaseManager>
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// FLEXRealmDatabaseManager.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 28/01/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
#if __has_include(<Realm/Realm.h>)
|
||||
#import <Realm/Realm.h>
|
||||
#import <Realm/RLMRealm_Dynamic.h>
|
||||
#else
|
||||
#import "FLEXRealmDefines.h"
|
||||
#endif
|
||||
|
||||
@interface FLEXRealmDatabaseManager ()
|
||||
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@property (nonatomic) RLMRealm *realm;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXRealmDatabaseManager
|
||||
static Class RLMRealmClass = nil;
|
||||
|
||||
+ (void)load {
|
||||
RLMRealmClass = NSClassFromString(@"RLMRealm");
|
||||
}
|
||||
|
||||
+ (instancetype)managerForDatabase:(NSString *)path {
|
||||
return [[self alloc] initWithPath:path];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
if (!RLMRealmClass) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_path = path;
|
||||
|
||||
if (![self open]) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
Class configurationClass = NSClassFromString(@"RLMRealmConfiguration");
|
||||
if (!RLMRealmClass || !configurationClass) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
NSError *error = nil;
|
||||
id configuration = [configurationClass new];
|
||||
[(RLMRealmConfiguration *)configuration setFileURL:[NSURL fileURLWithPath:self.path]];
|
||||
self.realm = [RLMRealmClass realmWithConfiguration:configuration error:&error];
|
||||
|
||||
return (error == nil);
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllTables {
|
||||
// Map each schema to its name
|
||||
NSArray<NSString *> *tableNames = [self.realm.schema.objectSchema flex_mapped:^id(RLMObjectSchema *schema, NSUInteger idx) {
|
||||
return schema.className ?: nil;
|
||||
}];
|
||||
|
||||
return [tableNames sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
|
||||
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
|
||||
// Map each column to its name
|
||||
return [objectSchema.properties flex_mapped:^id(RLMProperty *property, NSUInteger idx) {
|
||||
return property.name;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
|
||||
RLMObjectSchema *objectSchema = [self.realm.schema schemaForClassName:tableName];
|
||||
RLMResults *results = [self.realm allObjects:tableName];
|
||||
if (results.count == 0 || !objectSchema) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Map results to an array of rows
|
||||
return [NSArray flex_mapped:results block:^id(RLMObject *result, NSUInteger idx) {
|
||||
// Map each row to an array of the values of its properties
|
||||
return [objectSchema.properties flex_mapped:^id(RLMProperty *property, NSUInteger idx) {
|
||||
return [result valueForKey:property.name] ?: NSNull.null;
|
||||
}];
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// Realm.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tim Oliver on 16/02/2016.
|
||||
// Copyright © 2016 Realm. All rights reserved.
|
||||
//
|
||||
|
||||
#if __has_include(<Realm/Realm.h>)
|
||||
#else
|
||||
|
||||
@class RLMObject, RLMResults, RLMRealm, RLMRealmConfiguration, RLMSchema, RLMObjectSchema, RLMProperty;
|
||||
|
||||
@interface RLMRealmConfiguration : NSObject
|
||||
@property (nonatomic, copy) NSURL *fileURL;
|
||||
@end
|
||||
|
||||
@interface RLMRealm : NSObject
|
||||
@property (nonatomic, readonly) RLMSchema *schema;
|
||||
+ (RLMRealm *)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error;
|
||||
- (RLMResults *)allObjects:(NSString *)className;
|
||||
@end
|
||||
|
||||
@interface RLMSchema : NSObject
|
||||
@property (nonatomic, readonly) NSArray<RLMObjectSchema *> *objectSchema;
|
||||
- (RLMObjectSchema *)schemaForClassName:(NSString *)className;
|
||||
@end
|
||||
|
||||
@interface RLMObjectSchema : NSObject
|
||||
@property (nonatomic, readonly) NSString *className;
|
||||
@property (nonatomic, readonly) NSArray<RLMProperty *> *properties;
|
||||
@end
|
||||
|
||||
@interface RLMProperty : NSString
|
||||
@property (nonatomic, readonly) NSString *name;
|
||||
@end
|
||||
|
||||
@interface RLMResults : NSObject <NSFastEnumeration>
|
||||
@property (nonatomic, readonly) NSInteger count;
|
||||
@end
|
||||
|
||||
@interface RLMObject : NSObject
|
||||
|
||||
@end
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,48 @@
|
||||
//
|
||||
// FLEXSQLResult.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/3/20.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXSQLResult : NSObject
|
||||
|
||||
/// Describes the result of a non-select query, or an error of any kind of query
|
||||
+ (instancetype)message:(NSString *)message;
|
||||
/// Describes the result of a known failed execution
|
||||
+ (instancetype)error:(NSString *)message;
|
||||
|
||||
/// @param rowData A list of rows, where each element in the row
|
||||
/// corresponds to the column given in /c columnNames
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData;
|
||||
|
||||
@property (nonatomic, readonly, nullable) NSString *message;
|
||||
|
||||
/// A value of YES means this is surely an error,
|
||||
/// but it still might be an error even with a value of NO
|
||||
@property (nonatomic, readonly) BOOL isError;
|
||||
|
||||
/// A list of column names
|
||||
@property (nonatomic, readonly, nullable) NSArray<NSString *> *columns;
|
||||
/// A list of rows, where each element in the row corresponds
|
||||
/// to the value of the column at the same index in \c columns.
|
||||
///
|
||||
/// That is, given a row, looping over the contents of the row and
|
||||
/// the contents of \c columns will give you key-value pairs of
|
||||
/// column names to column values for that row.
|
||||
@property (nonatomic, readonly, nullable) NSArray<NSArray<NSString *> *> *rows;
|
||||
/// A list of rows where the fields are paired to column names.
|
||||
///
|
||||
/// This property is lazily constructed by looping over
|
||||
/// the rows and columns present in the other two properties.
|
||||
@property (nonatomic, readonly, nullable) NSArray<NSDictionary<NSString *, id> *> *keyedRows;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// FLEXSQLResult.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Tanner on 3/3/20.
|
||||
// Copyright © 2020 FLEX Team. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXSQLResult.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
|
||||
@implementation FLEXSQLResult
|
||||
@synthesize keyedRows = _keyedRows;
|
||||
|
||||
+ (instancetype)message:(NSString *)message {
|
||||
return [[self alloc] initWithMessage:message columns:nil rows:nil];
|
||||
}
|
||||
|
||||
+ (instancetype)error:(NSString *)message {
|
||||
FLEXSQLResult *result = [self message:message];
|
||||
result->_isError = YES;
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames rows:(NSArray<NSArray<NSString *> *> *)rowData {
|
||||
return [[self alloc] initWithMessage:nil columns:columnNames rows:rowData];
|
||||
}
|
||||
|
||||
- (instancetype)initWithMessage:(NSString *)message columns:(NSArray<NSString *> *)columns rows:(NSArray<NSArray<NSString *> *> *)rows {
|
||||
NSParameterAssert(message || (columns && rows));
|
||||
NSParameterAssert(rows.count == 0 || columns.count == rows.firstObject.count);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_message = message;
|
||||
_columns = columns;
|
||||
_rows = rows;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSArray<NSDictionary<NSString *,id> *> *)keyedRows {
|
||||
if (!_keyedRows) {
|
||||
_keyedRows = [self.rows flex_mapped:^id(NSArray<NSString *> *row, NSUInteger idx) {
|
||||
return [NSDictionary dictionaryWithObjects:row forKeys:self.columns];
|
||||
}];
|
||||
}
|
||||
|
||||
return _keyedRows;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,32 @@
|
||||
//
|
||||
// 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>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
#import "FLEXSQLResult.h"
|
||||
|
||||
@interface FLEXSQLiteDatabaseManager : NSObject <FLEXDatabaseManager>
|
||||
|
||||
/// Contains the result of the last operation, which may be an error
|
||||
@property (nonatomic, readonly) FLEXSQLResult *lastResult;
|
||||
/// Calls into \c sqlite3_last_insert_rowid()
|
||||
@property (nonatomic, readonly) NSInteger lastRowID;
|
||||
|
||||
/// Given a statement like 'SELECT * from @table where @col = @val' and arguments
|
||||
/// like { @"table": @"Album", @"col": @"year", @"val" @1 } this method will
|
||||
/// invoke the statement and properly bind the given arguments to the statement.
|
||||
///
|
||||
/// You may pass NSStrings, NSData, NSNumbers, or NSNulls as values.
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)statement arguments:(NSDictionary<NSString *, id> *)args;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,329 @@
|
||||
//
|
||||
// PTDatabaseManager.m
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXSQLiteDatabaseManager.h"
|
||||
#import "FLEXManager.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXRuntimeConstants.h"
|
||||
#import <sqlite3.h>
|
||||
|
||||
#define kQuery(name, str) static NSString * const QUERY_##name = str
|
||||
|
||||
kQuery(TABLENAMES, @"SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
|
||||
kQuery(ROWIDS, @"SELECT rowid FROM \"%@\" ORDER BY rowid ASC");
|
||||
|
||||
@interface FLEXSQLiteDatabaseManager ()
|
||||
@property (nonatomic) sqlite3 *db;
|
||||
@property (nonatomic, copy) NSString *path;
|
||||
@end
|
||||
|
||||
@implementation FLEXSQLiteDatabaseManager
|
||||
|
||||
#pragma mark - FLEXDatabaseManager
|
||||
|
||||
+ (instancetype)managerForDatabase:(NSString *)path {
|
||||
return [[self alloc] initWithPath:path];
|
||||
}
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self.path = path;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self close];
|
||||
}
|
||||
|
||||
- (BOOL)open {
|
||||
if (self.db) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
int err = sqlite3_open(self.path.UTF8String, &_db);
|
||||
|
||||
#if SQLITE_HAS_CODEC
|
||||
NSString *defaultSqliteDatabasePassword = FLEXManager.sharedManager.defaultSqliteDatabasePassword;
|
||||
if (defaultSqliteDatabasePassword) {
|
||||
const char *key = defaultSqliteDatabasePassword.UTF8String;
|
||||
sqlite3_key(_db, key, (int)strlen(key));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (err != SQLITE_OK) {
|
||||
return [self storeErrorForLastTask:@"Open"];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)close {
|
||||
if (!self.db) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
int rc;
|
||||
BOOL retry, 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) {
|
||||
[self storeErrorForLastTask:@"Close"];
|
||||
self.db = nil;
|
||||
return NO;
|
||||
}
|
||||
} while (retry);
|
||||
|
||||
self.db = nil;
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSInteger)lastRowID {
|
||||
return (NSInteger)sqlite3_last_insert_rowid(self.db);
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllTables {
|
||||
return [[self executeStatement:QUERY_TABLENAMES].rows flex_mapped:^id(NSArray *table, NSUInteger idx) {
|
||||
return table.firstObject;
|
||||
}] ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryAllColumnsOfTable:(NSString *)tableName {
|
||||
NSString *sql = [NSString stringWithFormat:@"PRAGMA table_info('%@')",tableName];
|
||||
FLEXSQLResult *results = [self executeStatement:sql];
|
||||
|
||||
// https://github.com/FLEXTool/FLEX/issues/554
|
||||
if (!results.keyedRows.count) {
|
||||
sql = [NSString stringWithFormat:@"SELECT * FROM pragma_table_info('%@')", tableName];
|
||||
results = [self executeStatement:sql];
|
||||
|
||||
// Fallback to empty query
|
||||
if (!results.keyedRows.count) {
|
||||
sql = [NSString stringWithFormat:@"SELECT * FROM \"%@\" where 0=1", tableName];
|
||||
return [self executeStatement:sql].columns ?: @[];
|
||||
}
|
||||
}
|
||||
|
||||
return [results.keyedRows flex_mapped:^id(NSDictionary *column, NSUInteger idx) {
|
||||
return column[@"name"];
|
||||
}] ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSArray *> *)queryAllDataInTable:(NSString *)tableName {
|
||||
NSString *command = [NSString stringWithFormat:@"SELECT * FROM \"%@\"", tableName];
|
||||
return [self executeStatement:command].rows ?: @[];
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)queryRowIDsInTable:(NSString *)tableName {
|
||||
NSString *command = [NSString stringWithFormat:QUERY_ROWIDS, tableName];
|
||||
NSArray<NSArray<NSString *> *> *data = [self executeStatement:command].rows ?: @[];
|
||||
|
||||
return [data flex_mapped:^id(NSArray<NSString *> *obj, NSUInteger idx) {
|
||||
return obj.firstObject;
|
||||
}];
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)sql {
|
||||
return [self executeStatement:sql arguments:nil];
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)executeStatement:(NSString *)sql arguments:(NSDictionary *)args {
|
||||
[self open];
|
||||
|
||||
FLEXSQLResult *result = nil;
|
||||
|
||||
sqlite3_stmt *pstmt;
|
||||
int status;
|
||||
if ((status = sqlite3_prepare_v2(_db, sql.UTF8String, -1, &pstmt, 0)) == SQLITE_OK) {
|
||||
NSMutableArray<NSArray *> *rows = [NSMutableArray new];
|
||||
|
||||
// Bind parameters, if any
|
||||
if (![self bindParameters:args toStatement:pstmt]) {
|
||||
return self.lastResult;
|
||||
}
|
||||
|
||||
// Grab columns (columnCount will be 0 for insert/update/delete)
|
||||
int columnCount = sqlite3_column_count(pstmt);
|
||||
NSArray<NSString *> *columns = [NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
|
||||
return @(sqlite3_column_name(pstmt, (int)i));
|
||||
}];
|
||||
|
||||
// Execute statement
|
||||
while ((status = sqlite3_step(pstmt)) == SQLITE_ROW) {
|
||||
// Grab rows if this is a selection query
|
||||
int dataCount = sqlite3_data_count(pstmt);
|
||||
if (dataCount > 0) {
|
||||
[rows addObject:[NSArray flex_forEachUpTo:columnCount map:^id(NSUInteger i) {
|
||||
return [self objectForColumnIndex:(int)i stmt:pstmt];
|
||||
}]];
|
||||
}
|
||||
}
|
||||
|
||||
if (status == SQLITE_DONE) {
|
||||
// columnCount will be 0 for insert/update/delete
|
||||
if (rows.count || columnCount > 0) {
|
||||
// We executed a SELECT query
|
||||
result = _lastResult = [FLEXSQLResult columns:columns rows:rows];
|
||||
} else {
|
||||
// We executed a query like INSERT, UDPATE, or DELETE
|
||||
int rowsAffected = sqlite3_changes(_db);
|
||||
NSString *message = [NSString stringWithFormat:@"%d row(s) affected", rowsAffected];
|
||||
result = _lastResult = [FLEXSQLResult message:message];
|
||||
}
|
||||
} else {
|
||||
// An error occured executing the query
|
||||
result = _lastResult = [self errorResult:@"Execution"];
|
||||
}
|
||||
} else {
|
||||
// An error occurred creating the prepared statement
|
||||
result = _lastResult = [self errorResult:@"Prepared statement"];
|
||||
}
|
||||
|
||||
sqlite3_finalize(pstmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
/// @return YES on success, NO if an error was encountered and stored in \c lastResult
|
||||
- (BOOL)bindParameters:(NSDictionary *)args toStatement:(sqlite3_stmt *)pstmt {
|
||||
for (NSString *param in args.allKeys) {
|
||||
int status = SQLITE_OK, idx = sqlite3_bind_parameter_index(pstmt, param.UTF8String);
|
||||
id value = args[param];
|
||||
|
||||
if (idx == 0) {
|
||||
// No parameter matching that arg
|
||||
@throw NSInternalInconsistencyException;
|
||||
}
|
||||
|
||||
// Null
|
||||
if ([value isKindOfClass:[NSNull class]]) {
|
||||
status = sqlite3_bind_null(pstmt, idx);
|
||||
}
|
||||
// String params
|
||||
else if ([value isKindOfClass:[NSString class]]) {
|
||||
const char *str = [value UTF8String];
|
||||
status = sqlite3_bind_text(pstmt, idx, str, (int)strlen(str), SQLITE_TRANSIENT);
|
||||
}
|
||||
// Data params
|
||||
else if ([value isKindOfClass:[NSData class]]) {
|
||||
const void *blob = [value bytes];
|
||||
status = sqlite3_bind_blob64(pstmt, idx, blob, [value length], SQLITE_TRANSIENT);
|
||||
}
|
||||
// Primitive params
|
||||
else if ([value isKindOfClass:[NSNumber class]]) {
|
||||
FLEXTypeEncoding type = [value objCType][0];
|
||||
switch (type) {
|
||||
case FLEXTypeEncodingCBool:
|
||||
case FLEXTypeEncodingChar:
|
||||
case FLEXTypeEncodingUnsignedChar:
|
||||
case FLEXTypeEncodingShort:
|
||||
case FLEXTypeEncodingUnsignedShort:
|
||||
case FLEXTypeEncodingInt:
|
||||
case FLEXTypeEncodingUnsignedInt:
|
||||
case FLEXTypeEncodingLong:
|
||||
case FLEXTypeEncodingUnsignedLong:
|
||||
case FLEXTypeEncodingLongLong:
|
||||
case FLEXTypeEncodingUnsignedLongLong:
|
||||
status = sqlite3_bind_int64(pstmt, idx, (sqlite3_int64)[value longValue]);
|
||||
break;
|
||||
|
||||
case FLEXTypeEncodingFloat:
|
||||
case FLEXTypeEncodingDouble:
|
||||
status = sqlite3_bind_double(pstmt, idx, [value doubleValue]);
|
||||
break;
|
||||
|
||||
default:
|
||||
@throw NSInternalInconsistencyException;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Unsupported type
|
||||
else {
|
||||
@throw NSInternalInconsistencyException;
|
||||
}
|
||||
|
||||
if (status != SQLITE_OK) {
|
||||
return [self storeErrorForLastTask:
|
||||
[NSString stringWithFormat:@"Binding param named '%@'", param]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL)storeErrorForLastTask:(NSString *)action {
|
||||
_lastResult = [self errorResult:action];
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (FLEXSQLResult *)errorResult:(NSString *)description {
|
||||
const char *error = sqlite3_errmsg(_db);
|
||||
NSString *message = error ? @(error) : [NSString
|
||||
stringWithFormat:@"(%@: empty error)", description
|
||||
];
|
||||
|
||||
return [FLEXSQLResult error:message];
|
||||
}
|
||||
|
||||
- (id)objectForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt*)stmt {
|
||||
int columnType = sqlite3_column_type(stmt, columnIdx);
|
||||
|
||||
switch (columnType) {
|
||||
case SQLITE_INTEGER:
|
||||
return @(sqlite3_column_int64(stmt, columnIdx)).stringValue;
|
||||
case SQLITE_FLOAT:
|
||||
return @(sqlite3_column_double(stmt, columnIdx)).stringValue;
|
||||
case SQLITE_BLOB:
|
||||
return [NSString stringWithFormat:@"Data (%@ bytes)",
|
||||
@([self dataForColumnIndex:columnIdx stmt:stmt].length)
|
||||
];
|
||||
|
||||
default:
|
||||
// Default to a string for everything else
|
||||
return [self stringForColumnIndex:columnIdx stmt:stmt] ?: NSNull.null;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)stringForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
|
||||
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || columnIdx < 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const char *text = (const char *)sqlite3_column_text(stmt, columnIdx);
|
||||
return text ? @(text) : nil;
|
||||
}
|
||||
|
||||
- (NSData *)dataForColumnIndex:(int)columnIdx stmt:(sqlite3_stmt *)stmt {
|
||||
if (sqlite3_column_type(stmt, columnIdx) == SQLITE_NULL || (columnIdx < 0)) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
const void *blob = sqlite3_column_blob(stmt, columnIdx);
|
||||
NSInteger size = (NSInteger)sqlite3_column_bytes(stmt, columnIdx);
|
||||
|
||||
return blob ? [NSData dataWithBytes:blob length:size] : nil;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.h
|
||||
// FLEX
|
||||
//
|
||||
// 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,
|
||||
};
|
||||
|
||||
NS_INLINE FLEXTableColumnHeaderSortType FLEXNextTableColumnHeaderSortType(
|
||||
FLEXTableColumnHeaderSortType current) {
|
||||
switch (current) {
|
||||
case FLEXTableColumnHeaderSortTypeAsc:
|
||||
return FLEXTableColumnHeaderSortTypeDesc;
|
||||
case FLEXTableColumnHeaderSortTypeNone:
|
||||
case FLEXTableColumnHeaderSortTypeDesc:
|
||||
return FLEXTableColumnHeaderSortTypeAsc;
|
||||
}
|
||||
|
||||
return FLEXTableColumnHeaderSortTypeNone;
|
||||
}
|
||||
|
||||
@interface FLEXTableColumnHeader : UIView
|
||||
|
||||
@property (nonatomic) NSInteger index;
|
||||
@property (nonatomic, readonly) UILabel *titleLabel;
|
||||
|
||||
@property (nonatomic) FLEXTableColumnHeaderSortType sortType;
|
||||
|
||||
@end
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// FLEXTableContentHeaderCell.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/26.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableColumnHeader.h"
|
||||
#import "FLEXColor.h"
|
||||
#import "UIFont+FLEX.h"
|
||||
#import "FLEXUtility.h"
|
||||
|
||||
static const CGFloat kMargin = 5;
|
||||
static const CGFloat kArrowWidth = 20;
|
||||
|
||||
@interface FLEXTableColumnHeader ()
|
||||
@property (nonatomic, readonly) UILabel *arrowLabel;
|
||||
@property (nonatomic, readonly) UIView *lineView;
|
||||
@end
|
||||
|
||||
@implementation FLEXTableColumnHeader
|
||||
|
||||
- (instancetype)initWithFrame:(CGRect)frame {
|
||||
self = [super initWithFrame:frame];
|
||||
if (self) {
|
||||
self.backgroundColor = FLEXColor.secondaryBackgroundColor;
|
||||
|
||||
_titleLabel = [UILabel new];
|
||||
_titleLabel.font = UIFont.flex_defaultTableCellFont;
|
||||
[self addSubview:_titleLabel];
|
||||
|
||||
_arrowLabel = [UILabel new];
|
||||
_arrowLabel.font = UIFont.flex_defaultTableCellFont;
|
||||
[self addSubview:_arrowLabel];
|
||||
|
||||
_lineView = [UIView new];
|
||||
_lineView.backgroundColor = FLEXColor.hairlineColor;
|
||||
[self addSubview:_lineView];
|
||||
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)setSortType:(FLEXTableColumnHeaderSortType)type {
|
||||
_sortType = type;
|
||||
|
||||
switch (type) {
|
||||
case FLEXTableColumnHeaderSortTypeNone:
|
||||
_arrowLabel.text = @"";
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeAsc:
|
||||
_arrowLabel.text = @"⬆️";
|
||||
break;
|
||||
case FLEXTableColumnHeaderSortTypeDesc:
|
||||
_arrowLabel.text = @"⬇️";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
|
||||
CGSize size = self.frame.size;
|
||||
|
||||
self.titleLabel.frame = CGRectMake(kMargin, 0, size.width - kArrowWidth - kMargin, size.height);
|
||||
self.arrowLabel.frame = CGRectMake(size.width - kArrowWidth, 0, kArrowWidth, size.height);
|
||||
self.lineView.frame = CGRectMake(size.width - 1, 2, FLEXPointsToPixels(1), size.height - 4);
|
||||
}
|
||||
|
||||
- (CGSize)sizeThatFits:(CGSize)size {
|
||||
CGFloat margins = kArrowWidth - 2 * kMargin;
|
||||
size = CGSizeMake(size.width - margins, size.height);
|
||||
CGFloat width = [_titleLabel sizeThatFits:size].width + margins;
|
||||
return CGSizeMake(width, size.height);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// PTTableContentViewController.h
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import "FLEXDatabaseManager.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface FLEXTableContentViewController : UIViewController
|
||||
|
||||
/// Display a mutable table with the given columns, rows, and name.
|
||||
///
|
||||
/// @param columnNames self explanatory.
|
||||
/// @param rowData an array of rows, where each row is an array of column data.
|
||||
/// @param rowIDs an array of stringy row IDs. Required for deleting rows.
|
||||
/// @param tableName an optional name of the table being viewed, if any. Enables adding rows.
|
||||
/// @param databaseManager an optional manager to allow modifying the table.
|
||||
/// Required for deleting rows. Required for adding rows if \c tableName is supplied.
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData
|
||||
rowIDs:(NSArray<NSString *> *)rowIDs
|
||||
tableName:(NSString *)tableName
|
||||
database:(id<FLEXDatabaseManager>)databaseManager;
|
||||
|
||||
/// Display an immutable table with the given columns and rows.
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
@@ -0,0 +1,359 @@
|
||||
//
|
||||
// PTTableContentViewController.m
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXTableContentViewController.h"
|
||||
#import "FLEXTableRowDataViewController.h"
|
||||
#import "FLEXMultiColumnTableView.h"
|
||||
#import "FLEXWebViewController.h"
|
||||
#import "FLEXUtility.h"
|
||||
#import "UIBarButtonItem+FLEX.h"
|
||||
|
||||
@interface FLEXTableContentViewController () <
|
||||
FLEXMultiColumnTableViewDataSource, FLEXMultiColumnTableViewDelegate
|
||||
>
|
||||
@property (nonatomic, readonly) NSArray<NSString *> *columns;
|
||||
@property (nonatomic) NSMutableArray<NSArray *> *rows;
|
||||
@property (nonatomic, readonly) NSString *tableName;
|
||||
@property (nonatomic, nullable) NSMutableArray<NSString *> *rowIDs;
|
||||
@property (nonatomic, readonly, nullable) id<FLEXDatabaseManager> databaseManager;
|
||||
|
||||
@property (nonatomic, readonly) BOOL canRefresh;
|
||||
|
||||
@property (nonatomic) FLEXMultiColumnTableView *multiColumnView;
|
||||
@end
|
||||
|
||||
@implementation FLEXTableContentViewController
|
||||
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData
|
||||
rowIDs:(NSArray<NSString *> *)rowIDs
|
||||
tableName:(NSString *)tableName
|
||||
database:(id<FLEXDatabaseManager>)databaseManager {
|
||||
return [[self alloc]
|
||||
initWithColumns:columnNames
|
||||
rows:rowData
|
||||
rowIDs:rowIDs
|
||||
tableName:tableName
|
||||
database:databaseManager
|
||||
];
|
||||
}
|
||||
|
||||
+ (instancetype)columns:(NSArray<NSString *> *)cols
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData {
|
||||
return [[self alloc] initWithColumns:cols rows:rowData rowIDs:nil tableName:nil database:nil];
|
||||
}
|
||||
|
||||
- (instancetype)initWithColumns:(NSArray<NSString *> *)columnNames
|
||||
rows:(NSArray<NSArray<NSString *> *> *)rowData
|
||||
rowIDs:(nullable NSArray<NSString *> *)rowIDs
|
||||
tableName:(nullable NSString *)tableName
|
||||
database:(nullable id<FLEXDatabaseManager>)databaseManager {
|
||||
// Must supply all optional parameters as one, or none
|
||||
BOOL all = rowIDs && tableName && databaseManager;
|
||||
BOOL none = !rowIDs && !tableName && !databaseManager;
|
||||
NSParameterAssert(all || none);
|
||||
|
||||
self = [super init];
|
||||
if (self) {
|
||||
self->_columns = columnNames.copy;
|
||||
self->_rows = rowData.mutableCopy;
|
||||
self->_rowIDs = rowIDs.mutableCopy;
|
||||
self->_tableName = tableName.copy;
|
||||
self->_databaseManager = databaseManager;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadView {
|
||||
[super loadView];
|
||||
|
||||
[self.view addSubview:self.multiColumnView];
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.title = self.tableName;
|
||||
[self.multiColumnView reloadData];
|
||||
[self setupToolbarItems];
|
||||
}
|
||||
|
||||
- (FLEXMultiColumnTableView *)multiColumnView {
|
||||
if (!_multiColumnView) {
|
||||
_multiColumnView = [[FLEXMultiColumnTableView alloc]
|
||||
initWithFrame:FLEXRectSetSize(CGRectZero, self.view.frame.size)
|
||||
];
|
||||
|
||||
_multiColumnView.dataSource = self;
|
||||
_multiColumnView.delegate = self;
|
||||
}
|
||||
|
||||
return _multiColumnView;
|
||||
}
|
||||
|
||||
- (BOOL)canRefresh {
|
||||
return self.databaseManager && self.tableName;
|
||||
}
|
||||
|
||||
#pragma mark MultiColumnTableView DataSource
|
||||
|
||||
- (NSInteger)numberOfColumnsInTableView:(FLEXMultiColumnTableView *)tableView {
|
||||
return self.columns.count;
|
||||
}
|
||||
|
||||
- (NSInteger)numberOfRowsInTableView:(FLEXMultiColumnTableView *)tableView {
|
||||
return self.rows.count;
|
||||
}
|
||||
|
||||
- (NSString *)columnTitle:(NSInteger)column {
|
||||
return self.columns[column];
|
||||
}
|
||||
|
||||
- (NSString *)rowTitle:(NSInteger)row {
|
||||
return @(row).stringValue;
|
||||
}
|
||||
|
||||
- (NSArray *)contentForRow:(NSInteger)row {
|
||||
return self.rows[row];
|
||||
}
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
heightForContentCellInRow:(NSInteger)row {
|
||||
return 40;
|
||||
}
|
||||
|
||||
- (CGFloat)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
minWidthForContentCellInColumn:(NSInteger)column {
|
||||
return 100;
|
||||
}
|
||||
|
||||
- (CGFloat)heightForTopHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
|
||||
return 40;
|
||||
}
|
||||
|
||||
- (CGFloat)widthForLeftHeaderInTableView:(FLEXMultiColumnTableView *)tableView {
|
||||
NSString *str = [NSString stringWithFormat:@"%lu",(unsigned long)self.rows.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 MultiColumnTableView Delegate
|
||||
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView didSelectRow:(NSInteger)row {
|
||||
NSArray<NSString *> *fields = [self.rows[row] flex_mapped:^id(NSString *field, NSUInteger idx) {
|
||||
return [NSString stringWithFormat:@"%@:\n%@", self.columns[idx], field];
|
||||
}];
|
||||
|
||||
NSArray<NSString *> *values = [self.rows[row] flex_mapped:^id(NSString *value, NSUInteger idx) {
|
||||
return [NSString stringWithFormat:@"'%@'", value];
|
||||
}];
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title([@"Row " stringByAppendingString:@(row).stringValue]);
|
||||
NSString *message = [fields componentsJoinedByString:@"\n\n"];
|
||||
make.message(message);
|
||||
make.button(@"Copy").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = message;
|
||||
});
|
||||
make.button(@"Copy as CSV").handler(^(NSArray<NSString *> *strings) {
|
||||
UIPasteboard.generalPasteboard.string = [values componentsJoinedByString:@", "];
|
||||
});
|
||||
make.button(@"Focus on Row").handler(^(NSArray<NSString *> *strings) {
|
||||
UIViewController *focusedRow = [FLEXTableRowDataViewController
|
||||
rows:[NSDictionary dictionaryWithObjects:self.rows[row] forKeys:self.columns]
|
||||
];
|
||||
[self.navigationController pushViewController:focusedRow animated:YES];
|
||||
});
|
||||
|
||||
// Option to delete row
|
||||
BOOL hasRowID = self.rows.count && row < self.rows.count;
|
||||
if (hasRowID && self.canRefresh) {
|
||||
make.button(@"Delete").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
NSString *deleteRow = [NSString stringWithFormat:
|
||||
@"DELETE FROM %@ WHERE rowid = %@",
|
||||
self.tableName, self.rowIDs[row]
|
||||
];
|
||||
|
||||
[self executeStatementAndShowResult:deleteRow completion:^(BOOL success) {
|
||||
// Remove deleted row and reload view
|
||||
if (success) {
|
||||
[self reloadTableDataFromDB];
|
||||
}
|
||||
}];
|
||||
});
|
||||
}
|
||||
|
||||
make.button(@"Dismiss").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
- (void)multiColumnTableView:(FLEXMultiColumnTableView *)tableView
|
||||
didSelectHeaderForColumn:(NSInteger)column
|
||||
sortType:(FLEXTableColumnHeaderSortType)sortType {
|
||||
|
||||
NSArray<NSArray *> *sortContentData = [self.rows
|
||||
sortedArrayWithOptions:NSSortStable
|
||||
usingComparator:^NSComparisonResult(NSArray *obj1, NSArray *obj2) {
|
||||
id a = obj1[column], b = obj2[column];
|
||||
if (a == NSNull.null) {
|
||||
return NSOrderedAscending;
|
||||
}
|
||||
if (b == NSNull.null) {
|
||||
return NSOrderedDescending;
|
||||
}
|
||||
|
||||
if ([a respondsToSelector:@selector(compare:options:)] &&
|
||||
[b respondsToSelector:@selector(compare:options:)]) {
|
||||
return [a compare:b options:NSNumericSearch];
|
||||
}
|
||||
|
||||
if ([a respondsToSelector:@selector(compare:)] && [b respondsToSelector:@selector(compare:)]) {
|
||||
return [a compare:b];
|
||||
}
|
||||
|
||||
return NSOrderedSame;
|
||||
}
|
||||
];
|
||||
|
||||
if (sortType == FLEXTableColumnHeaderSortTypeDesc) {
|
||||
sortContentData = sortContentData.reverseObjectEnumerator.allObjects.copy;
|
||||
}
|
||||
|
||||
self.rows = sortContentData.mutableCopy;
|
||||
[self.multiColumnView reloadData];
|
||||
}
|
||||
|
||||
#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) {
|
||||
self.multiColumnView.frame = CGRectMake(0, 32, self.view.frame.size.width, self.view.frame.size.height - 32);
|
||||
}
|
||||
else {
|
||||
self.multiColumnView.frame = CGRectMake(0, 64, self.view.frame.size.width, self.view.frame.size.height - 64);
|
||||
}
|
||||
|
||||
[self.view setNeedsLayout];
|
||||
} completion:nil];
|
||||
}
|
||||
|
||||
#pragma mark - Toolbar
|
||||
|
||||
- (void)setupToolbarItems {
|
||||
// We do not support modifying realm databases
|
||||
if (![self.databaseManager respondsToSelector:@selector(executeStatement:)]) {
|
||||
return;
|
||||
}
|
||||
|
||||
UIBarButtonItem *trashButton = FLEXBarButtonItemSystem(Trash, self, @selector(trashPressed));
|
||||
UIBarButtonItem *addButton = FLEXBarButtonItemSystem(Add, self, @selector(addPressed));
|
||||
|
||||
// Only allow adding rows or deleting rows if we have a table name
|
||||
trashButton.enabled = self.canRefresh;
|
||||
addButton.enabled = self.canRefresh;
|
||||
|
||||
self.toolbarItems = @[
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
addButton,
|
||||
UIBarButtonItem.flex_flexibleSpace,
|
||||
[trashButton flex_withTintColor:UIColor.redColor],
|
||||
];
|
||||
}
|
||||
|
||||
- (void)trashPressed {
|
||||
NSParameterAssert(self.tableName);
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Delete All Rows");
|
||||
make.message(@"All rows in this table will be permanently deleted.\nDo you want to proceed?");
|
||||
|
||||
make.button(@"Yes, I'm sure").destructiveStyle().handler(^(NSArray<NSString *> *strings) {
|
||||
NSString *deleteAll = [NSString stringWithFormat:@"DELETE FROM %@", self.tableName];
|
||||
[self executeStatementAndShowResult:deleteAll completion:^(BOOL success) {
|
||||
// Only dismiss on success
|
||||
if (success) {
|
||||
[self.navigationController popViewControllerAnimated:YES];
|
||||
}
|
||||
}];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
- (void)addPressed {
|
||||
NSParameterAssert(self.tableName);
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Add a New Row");
|
||||
make.message(@"Comma separate values to use in an INSERT statement.\n\n");
|
||||
make.message(@"INSERT INTO [table] VALUES (your_input)");
|
||||
make.textField(@"5, 'John Smith', 14,...");
|
||||
make.button(@"Insert").handler(^(NSArray<NSString *> *strings) {
|
||||
NSString *statement = [NSString stringWithFormat:
|
||||
@"INSERT INTO %@ VALUES (%@)", self.tableName, strings[0]
|
||||
];
|
||||
|
||||
[self executeStatementAndShowResult:statement completion:^(BOOL success) {
|
||||
if (success) {
|
||||
[self reloadTableDataFromDB];
|
||||
}
|
||||
}];
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
#pragma mark - Helpers
|
||||
|
||||
- (void)executeStatementAndShowResult:(NSString *)statement
|
||||
completion:(void (^_Nullable)(BOOL success))completion {
|
||||
NSParameterAssert(self.databaseManager);
|
||||
|
||||
FLEXSQLResult *result = [self.databaseManager executeStatement:statement];
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
if (result.isError) {
|
||||
make.title(@"Error");
|
||||
}
|
||||
|
||||
make.message(result.message ?: @"<no output>");
|
||||
make.button(@"Dismiss").cancelStyle().handler(^(NSArray<NSString *> *_) {
|
||||
if (completion) {
|
||||
completion(!result.isError);
|
||||
}
|
||||
});
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
- (void)reloadTableDataFromDB {
|
||||
if (!self.canRefresh) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray<NSArray *> *rows = [self.databaseManager queryAllDataInTable:self.tableName];
|
||||
NSArray<NSString *> *rowIDs = nil;
|
||||
if ([self.databaseManager respondsToSelector:@selector(queryRowIDsInTable:)]) {
|
||||
rowIDs = [self.databaseManager queryRowIDsInTable:self.tableName];
|
||||
}
|
||||
|
||||
self.rows = rows.mutableCopy;
|
||||
self.rowIDs = rowIDs.mutableCopy;
|
||||
[self.multiColumnView reloadData];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// FLEXTableLeftCell.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Peng Tao on 15/11/24.
|
||||
// Copyright © 2015年 f. All rights reserved.
|
||||
//
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface FLEXTableLeftCell : UITableViewCell
|
||||
|
||||
@property (nonatomic) UILabel *titlelabel;
|
||||
|
||||
+ (instancetype)cellWithTableView:(UITableView *)tableView;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// FLEXTableLeftCell.m
|
||||
// FLEX
|
||||
//
|
||||
// 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 new];
|
||||
textLabel.textAlignment = NSTextAlignmentCenter;
|
||||
textLabel.font = [UIFont systemFontOfSize:13.0];
|
||||
[cell.contentView addSubview:textLabel];
|
||||
cell.titlelabel = textLabel;
|
||||
}
|
||||
|
||||
return cell;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews {
|
||||
[super layoutSubviews];
|
||||
self.titlelabel.frame = self.contentView.frame;
|
||||
}
|
||||
@end
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// PTTableListViewController.h
|
||||
// PTDatabaseReader
|
||||
//
|
||||
// Created by Peng Tao on 15/11/23.
|
||||
// Copyright © 2015年 Peng Tao. All rights reserved.
|
||||
//
|
||||
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
|
||||
@interface FLEXTableListViewController : FLEXFilteringTableViewController
|
||||
|
||||
+ (BOOL)supportsExtension:(NSString *)extension;
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,165 @@
|
||||
//
|
||||
// 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 "FLEXSQLiteDatabaseManager.h"
|
||||
#import "FLEXRealmDatabaseManager.h"
|
||||
#import "FLEXTableContentViewController.h"
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "NSArray+FLEX.h"
|
||||
#import "FLEXAlert.h"
|
||||
#import "FLEXMacros.h"
|
||||
|
||||
@interface FLEXTableListViewController ()
|
||||
@property (nonatomic, readonly) id<FLEXDatabaseManager> dbm;
|
||||
@property (nonatomic, readonly) NSString *path;
|
||||
|
||||
@property (nonatomic, readonly) FLEXMutableListSection<NSString *> *tables;
|
||||
|
||||
+ (NSArray<NSString *> *)supportedSQLiteExtensions;
|
||||
+ (NSArray<NSString *> *)supportedRealmExtensions;
|
||||
|
||||
@end
|
||||
|
||||
@implementation FLEXTableListViewController
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
self = [super initWithStyle:UITableViewStyleGrouped];
|
||||
if (self) {
|
||||
_path = path.copy;
|
||||
_dbm = [self databaseManagerForFileAtPath:path];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
|
||||
self.showsSearchBar = YES;
|
||||
|
||||
// Compose query button //
|
||||
|
||||
UIBarButtonItem *composeQuery = [[UIBarButtonItem alloc]
|
||||
initWithBarButtonSystemItem:UIBarButtonSystemItemCompose
|
||||
target:self
|
||||
action:@selector(queryButtonPressed)
|
||||
];
|
||||
// Cannot run custom queries on realm databases
|
||||
composeQuery.enabled = [self.dbm
|
||||
respondsToSelector:@selector(executeStatement:)
|
||||
];
|
||||
|
||||
[self addToolbarItems:@[composeQuery]];
|
||||
}
|
||||
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections {
|
||||
_tables = [FLEXMutableListSection list:[self.dbm queryAllTables]
|
||||
cellConfiguration:^(__kindof UITableViewCell *cell, NSString *tableName, NSInteger row) {
|
||||
cell.textLabel.text = tableName;
|
||||
} filterMatcher:^BOOL(NSString *filterText, NSString *tableName) {
|
||||
return [tableName localizedCaseInsensitiveContainsString:filterText];
|
||||
}
|
||||
];
|
||||
|
||||
self.tables.selectionHandler = ^(FLEXTableListViewController *host, NSString *tableName) {
|
||||
NSArray *rows = [host.dbm queryAllDataInTable:tableName];
|
||||
NSArray *columns = [host.dbm queryAllColumnsOfTable:tableName];
|
||||
NSArray *rowIDs = nil;
|
||||
if ([host.dbm respondsToSelector:@selector(queryRowIDsInTable:)]) {
|
||||
rowIDs = [host.dbm queryRowIDsInTable:tableName];
|
||||
}
|
||||
UIViewController *resultsScreen = [FLEXTableContentViewController
|
||||
columns:columns rows:rows rowIDs:rowIDs tableName:tableName database:host.dbm
|
||||
];
|
||||
[host.navigationController pushViewController:resultsScreen animated:YES];
|
||||
};
|
||||
|
||||
return @[self.tables];
|
||||
}
|
||||
|
||||
- (void)reloadData {
|
||||
self.tables.customTitle = [NSString
|
||||
stringWithFormat:@"Tables (%@)", @(self.tables.filteredList.count)
|
||||
];
|
||||
|
||||
[super reloadData];
|
||||
}
|
||||
|
||||
- (void)queryButtonPressed {
|
||||
FLEXSQLiteDatabaseManager *database = self.dbm;
|
||||
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Execute an SQL query");
|
||||
make.textField(nil);
|
||||
make.button(@"Run").handler(^(NSArray<NSString *> *strings) {
|
||||
FLEXSQLResult *result = [database executeStatement:strings[0]];
|
||||
|
||||
if (result.message) {
|
||||
[FLEXAlert showAlert:@"Message" message:result.message from:self];
|
||||
} else {
|
||||
UIViewController *resultsScreen = [FLEXTableContentViewController
|
||||
columns:result.columns rows:result.rows
|
||||
];
|
||||
|
||||
[self.navigationController pushViewController:resultsScreen animated:YES];
|
||||
}
|
||||
});
|
||||
make.button(@"Cancel").cancelStyle();
|
||||
} showFrom:self];
|
||||
}
|
||||
|
||||
- (id<FLEXDatabaseManager>)databaseManagerForFileAtPath:(NSString *)path {
|
||||
NSString *pathExtension = path.pathExtension.lowercaseString;
|
||||
|
||||
NSArray<NSString *> *sqliteExtensions = FLEXTableListViewController.supportedSQLiteExtensions;
|
||||
if ([sqliteExtensions indexOfObject:pathExtension] != NSNotFound) {
|
||||
return [FLEXSQLiteDatabaseManager managerForDatabase:path];
|
||||
}
|
||||
|
||||
NSArray<NSString *> *realmExtensions = FLEXTableListViewController.supportedRealmExtensions;
|
||||
if (realmExtensions != nil && [realmExtensions indexOfObject:pathExtension] != NSNotFound) {
|
||||
return [FLEXRealmDatabaseManager managerForDatabase:path];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark - FLEXTableListViewController
|
||||
|
||||
+ (BOOL)supportsExtension:(NSString *)extension {
|
||||
extension = extension.lowercaseString;
|
||||
|
||||
NSArray<NSString *> *sqliteExtensions = FLEXTableListViewController.supportedSQLiteExtensions;
|
||||
if (sqliteExtensions.count > 0 && [sqliteExtensions indexOfObject:extension] != NSNotFound) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
NSArray<NSString *> *realmExtensions = FLEXTableListViewController.supportedRealmExtensions;
|
||||
if (realmExtensions.count > 0 && [realmExtensions indexOfObject:extension] != NSNotFound) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)supportedSQLiteExtensions {
|
||||
return @[@"db", @"sqlite", @"sqlite3"];
|
||||
}
|
||||
|
||||
+ (NSArray<NSString *> *)supportedRealmExtensions {
|
||||
if (NSClassFromString(@"RLMRealm") == nil) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return @[@"realm"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,14 @@
|
||||
//
|
||||
// FLEXTableRowDataViewController.h
|
||||
// FLEX
|
||||
//
|
||||
// Created by Chaoshuai Lu on 7/8/20.
|
||||
//
|
||||
|
||||
#import "FLEXFilteringTableViewController.h"
|
||||
|
||||
@interface FLEXTableRowDataViewController : FLEXFilteringTableViewController
|
||||
|
||||
+ (instancetype)rows:(NSDictionary<NSString *, id> *)rowData;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,54 @@
|
||||
//
|
||||
// FLEXTableRowDataViewController.m
|
||||
// FLEX
|
||||
//
|
||||
// Created by Chaoshuai Lu on 7/8/20.
|
||||
//
|
||||
|
||||
#import "FLEXTableRowDataViewController.h"
|
||||
#import "FLEXMutableListSection.h"
|
||||
#import "FLEXAlert.h"
|
||||
|
||||
@interface FLEXTableRowDataViewController ()
|
||||
@property (nonatomic) NSDictionary<NSString *, NSString *> *rowsByColumn;
|
||||
@end
|
||||
|
||||
@implementation FLEXTableRowDataViewController
|
||||
|
||||
#pragma mark - Initialization
|
||||
|
||||
+ (instancetype)rows:(NSDictionary<NSString *, id> *)rowData {
|
||||
FLEXTableRowDataViewController *controller = [self new];
|
||||
controller.rowsByColumn = rowData;
|
||||
return controller;
|
||||
}
|
||||
|
||||
#pragma mark - Overrides
|
||||
|
||||
- (NSArray<FLEXTableViewSection *> *)makeSections {
|
||||
NSDictionary<NSString *, NSString *> *rowsByColumn = self.rowsByColumn;
|
||||
|
||||
FLEXMutableListSection<NSString *> *section = [FLEXMutableListSection list:self.rowsByColumn.allKeys
|
||||
cellConfiguration:^(UITableViewCell *cell, NSString *column, NSInteger row) {
|
||||
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
|
||||
cell.textLabel.text = column;
|
||||
cell.detailTextLabel.text = rowsByColumn[column].description;
|
||||
} filterMatcher:^BOOL(NSString *filterText, NSString *column) {
|
||||
return [column localizedCaseInsensitiveContainsString:filterText] ||
|
||||
[rowsByColumn[column] localizedCaseInsensitiveContainsString:filterText];
|
||||
}
|
||||
];
|
||||
|
||||
section.selectionHandler = ^(UIViewController *host, NSString *column) {
|
||||
UIPasteboard.generalPasteboard.string = rowsByColumn[column].description;
|
||||
[FLEXAlert makeAlert:^(FLEXAlert *make) {
|
||||
make.title(@"Column Copied to Clipboard");
|
||||
make.message(rowsByColumn[column].description);
|
||||
make.button(@"Dismiss").cancelStyle();
|
||||
} showFrom:host];
|
||||
};
|
||||
|
||||
return @[section];
|
||||
}
|
||||
|
||||
@end
|
||||
21
Tweaks/FLEX/GlobalStateExplorers/DatabaseBrowser/LICENSE
Normal file
21
Tweaks/FLEX/GlobalStateExplorers/DatabaseBrowser/LICENSE
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user