├── .DS_Store ├── Browse.png ├── Query.png ├── README.md ├── SQLiteApp ├── .DS_Store ├── AppDelegate.swift ├── Assets.xcassets │ ├── .DS_Store │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_mac_128.png │ │ ├── icon_mac_128@2x.png │ │ ├── icon_mac_16.png │ │ ├── icon_mac_16@2x.png │ │ ├── icon_mac_256.png │ │ ├── icon_mac_256@2x.png │ │ ├── icon_mac_32.png │ │ ├── icon_mac_32@2x.png │ │ ├── icon_mac_512.png │ │ └── icon_mac_512@2x.png │ ├── Contents.json │ ├── arches.imageset │ │ ├── Contents.json │ │ ├── arches.png │ │ └── arches@2x.png │ ├── closeDB.imageset │ │ ├── CloseDB@2x.png │ │ └── Contents.json │ ├── database.imageset │ │ ├── Contents.json │ │ ├── DBDatabase-Sys.png │ │ └── DBDatabase-Sys@2x.png │ ├── first.imageset │ │ ├── Contents.json │ │ └── toolbar-first@2x.png │ ├── home.imageset │ │ ├── Contents.json │ │ └── SQLite-Home@2x.png │ ├── last.imageset │ │ ├── Contents.json │ │ └── toolbar-last@2x.png │ ├── next.imageset │ │ ├── Contents.json │ │ └── toolbar-next@2x.png │ ├── openDB.imageset │ │ ├── Contents.json │ │ └── openDB@2x.png │ ├── pre.imageset │ │ ├── Contents.json │ │ └── toolbar-pre@2x.png │ ├── table.imageset │ │ ├── Contents.json │ │ ├── DBTable-Sys.png │ │ └── DBTable-Sys@2x.png │ └── windowIcon.imageset │ │ ├── Contents.json │ │ └── icon_mac_32.png ├── Base.lproj │ └── Main.storyboard ├── Common │ ├── Config.swift │ ├── DispatchQueue+Ext.swift │ ├── JSONKit.swift │ ├── NSColor+Ext.swift │ └── NSPasteboard+Ext.swift ├── Components │ ├── .DS_Store │ ├── AutoLayoutX │ │ └── AutoLayoutX.swift │ ├── DataNavigationView │ │ ├── DataNavigationItem.swift │ │ └── DataNavigationView.swift │ ├── DataNavigationViewController │ │ ├── .DS_Store │ │ ├── Controller │ │ │ ├── TableDataNavigationViewController.swift │ │ │ ├── TableDataNavigationViewDelegate.swift │ │ │ ├── XibViewController.swift │ │ │ └── XibViewController.xib │ │ ├── DataPageManager │ │ │ └── DataPageManager.swift │ │ ├── Extension │ │ │ ├── .DS_Store │ │ │ ├── NSButton+ColumnItem.swift │ │ │ ├── NSComboBox+ColumnItem.swift │ │ │ ├── NSImageView+ColumnItem.swift │ │ │ ├── NSTableColumn+ColumnItem.swift │ │ │ ├── NSTableView+ColumnItem.swift │ │ │ └── NSTextField+ColumnItem.swift │ │ ├── Model │ │ │ └── TableColumnItem.swift │ │ └── TableDataDelegate │ │ │ └── TableDataDelegate.swift │ ├── DataStoreBO │ │ └── DataStoreBO.swift │ ├── MDatabase │ │ ├── .DS_Store │ │ ├── DBObject │ │ │ ├── DAO.swift │ │ │ ├── MDAO.swift │ │ │ ├── MDatabase.swift │ │ │ └── MModel.swift │ │ ├── Meta │ │ │ ├── DatabaseInfo.swift │ │ │ ├── FieldInfo.swift │ │ │ ├── MDatabase+Meta.swift │ │ │ ├── TableInfo.swift │ │ │ └── TypeKit.swift │ │ └── String+Regex.swift │ ├── MulticastDelegate │ │ └── TableListSelectionState.swift │ └── TreeViewDataDelegate │ │ ├── TreeNodeModel.swift │ │ └── TreeViewDataDelegate.swift ├── Controllers │ ├── .DS_Store │ ├── Browse │ │ ├── TableBrowseViewController.swift │ │ └── TableBrowseViewDelegate.swift │ ├── Main │ │ ├── AppMainSplitViewController.swift │ │ ├── AppMainSplitViewController.xib │ │ ├── AppMainTabViewController.swift │ │ └── AppMainWindowController.swift │ ├── Preferences │ │ ├── PreferencesViewController.swift │ │ └── PreferencesWindowController.swift │ ├── Query │ │ ├── TableQueryViewController.swift │ │ ├── TableQueryViewController.xib │ │ └── TableQueryViewController1.xib │ ├── Schema │ │ └── TableSchemaViewController.swift │ └── TableList │ │ ├── TableListView.swift │ │ ├── TableListViewController.swift │ │ ├── TableListViewController.xib │ │ └── TableListViewDelegate.swift ├── Info.plist ├── MacDev.sqlite ├── SQLiteApp-Bridging-Header.h └── Vendor │ ├── .DS_Store │ ├── GCDMulticastDelegate │ ├── GCDMulticastDelegate.h │ └── GCDMulticastDelegate.m │ └── fmdb │ ├── FMDB.h │ ├── FMDatabase.h │ ├── FMDatabase.m │ ├── FMDatabaseAdditions.h │ ├── FMDatabaseAdditions.m │ ├── FMDatabasePool.h │ ├── FMDatabasePool.m │ ├── FMDatabaseQueue.h │ ├── FMDatabaseQueue.m │ ├── FMResultSet.h │ ├── FMResultSet.m │ └── MDatabase-Bridging-Header.h ├── SQLiteAppLogicArchitecture.png ├── Schema.png └── SwiftSQLiteApp.xcodeproj ├── project.pbxproj ├── project.xcworkspace ├── contents.xcworkspacedata └── xcuserdata │ ├── idevfans.xcuserdatad │ └── UserInterfaceState.xcuserstate │ └── zhaojw.xcuserdatad │ └── UserInterfaceState.xcuserstate └── xcuserdata ├── idevfans.xcuserdatad ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist └── xcschemes │ ├── SwiftSQLiteApp.xcscheme │ └── xcschememanagement.plist └── zhaojw.xcuserdatad ├── xcdebugger └── Breakpoints_v2.xcbkptlist └── xcschemes └── xcschememanagement.plist /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/.DS_Store -------------------------------------------------------------------------------- /Browse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/Browse.png -------------------------------------------------------------------------------- /Query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/Query.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftSQLiteApp 2 | simple SQLite database management for OS X,this is an unfinished project http://www.macdev.io/ 3 | 4 | ![SQLiteApp Logic Architecture](https://github.com/javaliker/SwiftSQLiteApp/blob/master/SQLiteAppLogicArchitecture.png) 5 | 6 | ![Browse Data](https://github.com/javaliker/SwiftSQLiteApp/blob/master/Browse.png) 7 | 8 | ![Schema](https://github.com/javaliker/SwiftSQLiteApp/blob/master/Schema.png) 9 | 10 | ![Query](https://github.com/javaliker/SwiftSQLiteApp/blob/master/Query.png) 11 | -------------------------------------------------------------------------------- /SQLiteApp/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/7. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | @NSApplicationMain 12 | class AppDelegate: NSObject, NSApplicationDelegate { 13 | 14 | lazy var preferencesWindowController: PreferencesWindowController = { 15 | let sb = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: Bundle.main) 16 | let pWVC = sb.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "Preferences")) as! PreferencesWindowController 17 | return pWVC 18 | }() 19 | 20 | 21 | @IBAction func showPreferenceWindowController(_ sender: AnyObject) { 22 | self.preferencesWindowController.showWindow(self) 23 | } 24 | 25 | 26 | func applicationDidFinishLaunching(_ aNotification: Notification) { 27 | // Insert code here to initialize your application 28 | print("applicationDidFinishLaunching") 29 | } 30 | 31 | 32 | } 33 | 34 | -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "512x512", 5 | "idiom" : "mac", 6 | "filename" : "icon_mac_512.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "512x512", 11 | "idiom" : "mac", 12 | "filename" : "icon_mac_512@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "16x16", 17 | "idiom" : "mac", 18 | "filename" : "icon_mac_16.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "16x16", 23 | "idiom" : "mac", 24 | "filename" : "icon_mac_16@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_mac_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_mac_128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "32x32", 41 | "idiom" : "mac", 42 | "filename" : "icon_mac_32.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "32x32", 47 | "idiom" : "mac", 48 | "filename" : "icon_mac_32@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "256x256", 53 | "idiom" : "mac", 54 | "filename" : "icon_mac_256.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "256x256", 59 | "idiom" : "mac", 60 | "filename" : "icon_mac_256@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_128.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_128@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_16.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_16@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_256.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_256@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_32.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_32@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_512.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/AppIcon.appiconset/icon_mac_512@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/arches.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "arches.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "arches@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/arches.imageset/arches.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/arches.imageset/arches.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/arches.imageset/arches@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/arches.imageset/arches@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/closeDB.imageset/CloseDB@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/closeDB.imageset/CloseDB@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/closeDB.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "CloseDB@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/database.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "DBDatabase-Sys.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "DBDatabase-Sys@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/database.imageset/DBDatabase-Sys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/database.imageset/DBDatabase-Sys.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/database.imageset/DBDatabase-Sys@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/database.imageset/DBDatabase-Sys@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/first.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "toolbar-first@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/first.imageset/toolbar-first@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/first.imageset/toolbar-first@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/home.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "SQLite-Home@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/home.imageset/SQLite-Home@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/home.imageset/SQLite-Home@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/last.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "toolbar-last@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/last.imageset/toolbar-last@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/last.imageset/toolbar-last@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/next.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "toolbar-next@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/next.imageset/toolbar-next@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/next.imageset/toolbar-next@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/openDB.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "openDB@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/openDB.imageset/openDB@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/openDB.imageset/openDB@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/pre.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "toolbar-pre@2x.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/pre.imageset/toolbar-pre@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/pre.imageset/toolbar-pre@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/table.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "DBTable-Sys.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "DBTable-Sys@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/table.imageset/DBTable-Sys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/table.imageset/DBTable-Sys.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/table.imageset/DBTable-Sys@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/table.imageset/DBTable-Sys@2x.png -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/windowIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "filename" : "icon_mac_32.png", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SQLiteApp/Assets.xcassets/windowIcon.imageset/icon_mac_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Assets.xcassets/windowIcon.imageset/icon_mac_32.png -------------------------------------------------------------------------------- /SQLiteApp/Common/Config.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Config.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/7. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class Config: NSObject { 12 | 13 | private static let sharedInstance = Config() 14 | class var shared: Config { 15 | return sharedInstance 16 | } 17 | 18 | lazy var queue: DispatchQueue = { 19 | let queue = DispatchQueue(label: "macdec.io.SQLiteApp") 20 | return queue 21 | }() 22 | 23 | lazy var main: DispatchQueue = { 24 | let queue = DispatchQueue.main 25 | return queue 26 | }() 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SQLiteApp/Common/DispatchQueue+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DispatchQueue+Ext.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/7. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension DispatchQueue { 12 | 13 | private static let sharedQueueInstance = DispatchQueue(label: "macdec.io.SQLiteApp") 14 | 15 | static let back = DispatchQueue(label: "macdec.io.SQLiteApp") 16 | 17 | // static func back() ->DispatchQueue { 18 | // return sharedQueueInstance 19 | // } 20 | 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /SQLiteApp/Common/JSONKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONKit.swift 3 | // OpenMacX 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | import Foundation 12 | 13 | extension String { 14 | 15 | func jsonObject() -> [String:Any]? { 16 | let jsonData = self.data(using: String.Encoding.utf8)! 17 | return jsonData.jsonObject() 18 | } 19 | 20 | } 21 | 22 | extension Array { 23 | 24 | func jsonData() ->Data? { 25 | let data = try? JSONSerialization.data(withJSONObject: self , options: [.prettyPrinted]) 26 | return data 27 | } 28 | 29 | func jsonString() ->String? { 30 | var string: String? 31 | if let data = self.jsonData(){ 32 | string = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) 33 | } 34 | return string 35 | } 36 | 37 | } 38 | 39 | extension Dictionary { 40 | 41 | func jsonData() ->Data? { 42 | let data = try? JSONSerialization.data(withJSONObject: self, options: [.prettyPrinted]) 43 | return data 44 | } 45 | 46 | func jsonString() ->String? { 47 | var string: String? 48 | if let data = self.jsonData(){ 49 | string = String(data: data, encoding: String.Encoding(rawValue: String.Encoding.utf8.rawValue)) 50 | } 51 | return string 52 | } 53 | } 54 | 55 | extension Data { 56 | 57 | func jsonObject() -> [String:Any]? { 58 | let jsonDict = try? JSONSerialization.jsonObject(with: self, options: [.allowFragments]) 59 | return jsonDict as? Dictionary 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /SQLiteApp/Common/NSColor+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSColor+Ext.swift 3 | // LocalizationTools 4 | // 5 | // Created by iDevFans on 16/8/27. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSColor { 12 | 13 | convenience init(red: Int, green: Int, blue: Int) { 14 | assert(red >= 0 && red <= 255, "Invalid red component") 15 | assert(green >= 0 && green <= 255, "Invalid green component") 16 | assert(blue >= 0 && blue <= 255, "Invalid blue component") 17 | 18 | self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1.0) 19 | } 20 | 21 | convenience init(hex:Int) { 22 | self.init(red:(hex >> 16) & 0xff, green:(hex >> 8) & 0xff, blue:hex & 0xff) 23 | } 24 | 25 | 26 | static func toolBarBackgroundColor() -> NSColor { 27 | return NSColor(patternImage: NSImage(named: NSImage.Name(rawValue: "arches"))!) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SQLiteApp/Common/NSPasteboard+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // sfsf.swift 3 | // ImageAassetAutomator 4 | // 5 | // Created by zhaojw on 2017/10/31. 6 | // Copyright © 2017年 macdev. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | extension NSPasteboard { 12 | 13 | class func copyString(str: String, owner: AnyObject) { 14 | let pb = NSPasteboard.general 15 | let types = [NSPasteboard.PasteboardType.string] 16 | pb.declareTypes(types, owner: owner) 17 | if str != "" { 18 | pb.setString(str, forType: NSPasteboard.PasteboardType.string) 19 | } 20 | } 21 | 22 | } 23 | 24 | 25 | extension NSPasteboard.PasteboardType { 26 | static let backwardsCompatibleFileURL: NSPasteboard.PasteboardType = { 27 | if #available(OSX 10.13, *) { 28 | return NSPasteboard.PasteboardType.fileURL 29 | } else { 30 | return NSPasteboard.PasteboardType(kUTTypeFileURL as String) 31 | } 32 | 33 | } () 34 | } 35 | -------------------------------------------------------------------------------- /SQLiteApp/Components/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Components/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationView/DataNavigationItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataNavigationItem.swift 3 | // DataNavigationView 4 | // 5 | // Created by iDevFans on 16/8/17. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | public enum DataNavigationViewButtonActionType : Int { 12 | 13 | case add 14 | 15 | case remove 16 | 17 | case refresh 18 | 19 | case first 20 | 21 | case pre 22 | 23 | case next 24 | 25 | case last 26 | 27 | } 28 | 29 | 30 | class DataNavigationItem: NSObject { 31 | var tooltips: String? //鼠标悬停的提示 32 | var identifier: String?//标识字符串 33 | var tag: DataNavigationViewButtonActionType? //按钮的tag 34 | } 35 | 36 | class DataNavigationTextItem: DataNavigationItem { 37 | var title: String? //用作label的标题 38 | var textColor: NSColor? //文本颜色 39 | var alignment: NSTextAlignment = .center //文本的位置 40 | } 41 | 42 | class DataNavigationButtonItem: DataNavigationItem { 43 | var imageName: String?//按钮的图标名称 44 | } 45 | 46 | class DataNavigationFlexibleItem: DataNavigationItem { 47 | 48 | } 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationView/DataNavigationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataNavigationView.swift 3 | // DataNavigationView 4 | // 5 | // Created by iDevFans on 16/8/17. 6 | // Copyright © 2016年 macdev. All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | let kToolbarPreImageName = "pre" 13 | let kToolbarFirstImageName = "first" 14 | let kToolbarNextImageName = "next" 15 | let kToolbarLastImageName = "last" 16 | 17 | let kInfoIdentifier = "info" 18 | let kPagesIdentifier = "pages" 19 | 20 | class DataNavigationView: NSView { 21 | var items: [AnyObject] = [] 22 | var leftViews: [NSView] = [] //左边按钮视图集 23 | var flexibleView: NSView? //中间Center区域 24 | var rightViews: [NSView] = [] //右边按钮视图集 25 | 26 | //顶部一条横线 27 | lazy var topLine:NSBox = { 28 | let line = NSBox() 29 | //使用NSBox控件的分隔线模式 30 | line.boxType = .separator 31 | line.translatesAutoresizingMaskIntoConstraints = false 32 | return line 33 | }() 34 | weak var target: AnyObject? 35 | 36 | var action: Selector? { 37 | 38 | didSet { 39 | let viewSet = NSSet(array: leftViews) 40 | viewSet.addingObjects(from: rightViews) 41 | for view in viewSet { 42 | if view is NSButton { 43 | let btn = view as! NSButton 44 | btn.target = self.target 45 | btn.action = self.action 46 | } 47 | } 48 | } 49 | } 50 | 51 | 52 | //配置默认的按钮组视图 53 | func setUpDefaultNavigationView() { 54 | let buttonItems = self.defaultItemsConfig() 55 | self.setUpNavigationViewWithItems(buttonItems) 56 | } 57 | 58 | //按items配置不同的按钮组 59 | func setUpNavigationViewWithItems(_ buttonItems: [AnyObject]) { 60 | self.items = buttonItems 61 | self.addNavigationItemsToView() 62 | } 63 | 64 | //默认的按钮配置 65 | func defaultItemsConfig() ->[AnyObject] { 66 | let item1 = DataNavigationButtonItem() 67 | item1.imageName = NSImage.Name.addTemplate.rawValue 68 | item1.tag = .add 69 | 70 | let item2 = DataNavigationButtonItem() 71 | item2.imageName = NSImage.Name.removeTemplate.rawValue 72 | item2.tag = .remove 73 | 74 | let item3 = DataNavigationButtonItem() 75 | item3.imageName = NSImage.Name.refreshTemplate.rawValue 76 | item3.tag = .refresh 77 | 78 | 79 | return [item1,item2,item3] 80 | } 81 | 82 | 83 | 84 | /* 85 | ------------------------------------------------- 86 | [B B B] [---------------------------][B B - B B] 87 | ------------------------------------------------- 88 | 89 | 中间区域是一个空白区域,可以做为信息面板,显示提示说明信息。 90 | 91 | */ 92 | 93 | func addNavigationItemsToView() { 94 | for view in self.subviews { 95 | view.removeFromSuperview() 96 | } 97 | self.addSubview(self.topLine) 98 | 99 | var hasFlexibleView: Bool = false 100 | var itemView: NSView? 101 | for item in self.items { 102 | itemView = nil 103 | if let buttonItem = item as? DataNavigationButtonItem { 104 | itemView = self.buttonWithItem(buttonItem) 105 | } 106 | else if let textItem = item as? DataNavigationTextItem { 107 | itemView = self.textLabelWithItem(textItem) 108 | } 109 | else if item is DataNavigationFlexibleItem { 110 | //中间区域,使用一个Text Lable占位 111 | self.flexibleView = self.infoLabel() 112 | self.addSubview(self.flexibleView!) 113 | hasFlexibleView = true 114 | } 115 | 116 | if let view = itemView { 117 | self.addSubview(view) 118 | if !hasFlexibleView { 119 | self.leftViews.append(view) 120 | } 121 | else { 122 | self.rightViews.append(view) 123 | } 124 | } 125 | } 126 | 127 | self.layoutNavigationView() 128 | } 129 | 130 | 131 | //对按钮组进行布局 132 | func layoutNavigationView() { 133 | 134 | //顶部一条横线布局配置(顶部,左边,右边) 135 | let topLineTop = self.topLine.topAnchor.constraint(equalTo: self.topAnchor, constant: 0) 136 | let topLineLeft = self.topLine.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0) 137 | let topLineRight = self.topLine.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0) 138 | //let topLineHeight = self.topLine.heightAnchor.constraint(equalToConstant: 1) 139 | NSLayoutConstraint.activate([topLineTop, topLineLeft, topLineRight]) 140 | 141 | 142 | 143 | var leftLastView: NSView? 144 | 145 | var left: NSLayoutConstraint? 146 | 147 | 148 | //当button是左边区域的第一个元素时,它的左边参考DataNavigationView的左边布局 149 | //否则参考leftLastView及button的左邻居布局 150 | for view in self.leftViews { 151 | let width = view.heightAnchor.constraint(equalToConstant: 24) 152 | let height = view.widthAnchor.constraint(equalToConstant: 24) 153 | let centerY = view.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0) 154 | if leftLastView == nil { 155 | left = view.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 4) 156 | } 157 | else { 158 | left = view.leftAnchor.constraint(equalTo: (leftLastView?.rightAnchor)!, constant: 4) 159 | } 160 | //激活自动布局约束,相当于增加约束到布局引擎 161 | NSLayoutConstraint.activate([left!, centerY, width, height]) 162 | //保存当前view做为下一次循环处理的button的左邻居 163 | leftLastView = view 164 | } 165 | 166 | if self.rightViews.count <= 0 { 167 | return 168 | } 169 | 170 | var rightLastView: NSView? 171 | //数组逆序 即 [2,3,7] = > [7,3,2] 172 | let reverseRightViews = self.rightViews.reversed() 173 | 174 | var right: NSLayoutConstraint? 175 | //从右边button区域最右边的第一个button的开始布局 176 | for view in reverseRightViews { 177 | 178 | let width = view.heightAnchor.constraint(equalToConstant: 24) 179 | let height = view.widthAnchor.constraint(equalToConstant: 24) 180 | let centerY = view.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0) 181 | if rightLastView == nil { 182 | right = view.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -4) 183 | } 184 | else { 185 | right = view.rightAnchor.constraint(equalTo: (rightLastView?.leftAnchor)!, constant: -4) 186 | } 187 | 188 | if view is NSTextField { 189 | let left = view.leftAnchor.constraint(equalTo: (rightLastView?.leftAnchor)!, constant: -64) 190 | NSLayoutConstraint.activate([left, right!, centerY]) 191 | } 192 | else { 193 | NSLayoutConstraint.activate([right!, centerY, width, height]) 194 | } 195 | 196 | rightLastView = view 197 | } 198 | 199 | 200 | //与Center区域右边相邻的第一个button 201 | let neighborButton = self.rightViews[0] 202 | 203 | //中间区域的约束,与左边,右边邻居各偏离4个像素 204 | let flexibleLeft = self.flexibleView?.leftAnchor.constraint(equalTo: (leftLastView?.rightAnchor)!, constant: 4) 205 | let flexibleRigth = self.flexibleView?.rightAnchor.constraint(equalTo: neighborButton.leftAnchor, constant: 4) 206 | let centerY = self.flexibleView?.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 0) 207 | 208 | NSLayoutConstraint.activate([flexibleLeft!, flexibleRigth!, centerY!]) 209 | } 210 | 211 | //根据item创建button 212 | func buttonWithItem(_ item:DataNavigationButtonItem) ->NSButton { 213 | let button = NSButton() 214 | //设置按钮图标 215 | button.image = NSImage(named: NSImage.Name(rawValue: item.imageName!)) 216 | button.setButtonType(.momentaryPushIn) 217 | //无边框 218 | button.isBordered = false 219 | button.bezelStyle = .rounded 220 | button.isEnabled = true 221 | button.state = .on 222 | if let identifier = item.identifier { 223 | button.identifier = NSUserInterfaceItemIdentifier(rawValue:identifier) 224 | } 225 | button.toolTip = item.tooltips 226 | //设置按钮事件响应方法 227 | button.target = self.target 228 | button.action = self.action 229 | button.tag = (item.tag?.rawValue)! 230 | button.translatesAutoresizingMaskIntoConstraints = false 231 | return button 232 | } 233 | 234 | 235 | //根据item创建Text Label 236 | func textLabelWithItem(_ item:DataNavigationTextItem) ->NSTextField { 237 | let textLabel = self.infoLabel() 238 | textLabel.stringValue = item.title! 239 | if let identifier = item.identifier { 240 | textLabel.identifier = NSUserInterfaceItemIdentifier(rawValue:identifier) 241 | } 242 | textLabel.textColor = item.textColor 243 | 244 | return textLabel 245 | } 246 | 247 | //创建一个空字串的占位视图 248 | func infoLabel() ->NSTextField { 249 | let label = NSTextField() 250 | label.alignment = .center 251 | label.identifier = NSUserInterfaceItemIdentifier(rawValue: kInfoIdentifier) 252 | label.font = NSFont.labelFont(ofSize: 12) 253 | label.stringValue = "" 254 | label.isBezeled = false 255 | label.drawsBackground = false 256 | label.isEditable = false 257 | label.isSelectable = false 258 | label.translatesAutoresizingMaskIntoConstraints = false 259 | return label 260 | } 261 | 262 | //根据identifier更新文本串的字符 263 | func updateLabelWithIdentifier(_ identifier:String, title labelTitle: String) { 264 | for view in self.subviews { 265 | if view.identifier?.rawValue == identifier { 266 | if let label = view as? NSTextField { 267 | label.stringValue = labelTitle 268 | break 269 | } 270 | } 271 | } 272 | } 273 | 274 | 275 | //根据identifier更新文本串的字符 276 | func updateInfoLabel(title labelTitle: String) { 277 | self.updateLabelWithIdentifier(kInfoIdentifier, title: labelTitle) 278 | } 279 | 280 | //根据identifier更新文本串的字符 281 | func updatePagesLabel(title labelTitle: String) { 282 | self.updateLabelWithIdentifier(kPagesIdentifier, title: labelTitle) 283 | } 284 | 285 | 286 | } 287 | 288 | 289 | 290 | 291 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Components/DataNavigationViewController/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Controller/TableDataNavigationViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableDataNavigationViewController.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/21. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | 10 | import Cocoa 11 | let kPageSize: Int = 20 12 | class TableDataNavigationViewController: XibViewController { 13 | 14 | lazy var pageManager: DataPageManager = { 15 | let dataPageManager = DataPageManager.init(pageSize: kPageSize) 16 | return dataPageManager 17 | }() 18 | 19 | deinit { 20 | self.tableView?.unregisterDraggedTypes() 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | //自动布局 26 | self.setupAutolayout() 27 | 28 | //配置表格样式 29 | self.tableViewStyleConfig() 30 | //设置表格代理 31 | self.tableDelegateConfig() 32 | 33 | //配置表格列 34 | self.tableViewColumnConfig() 35 | 36 | //关联导航面板按钮事件 37 | self.dataNavigationView?.target = self 38 | self.dataNavigationView?.action = #selector(self.toolButtonClicked(_:)) 39 | //配置导航面板的按钮 40 | self.dataNavigationView?.setUpNavigationViewWithItems(self.dataNavigationItemsConfig()) 41 | 42 | //注册拖放 43 | self.registerRowDrag() 44 | 45 | } 46 | 47 | 48 | func setupAutolayout() { 49 | //如果存在xib,则说明不需要通过代码设置自动布局 50 | if self.tableXibView() != nil { 51 | return 52 | } 53 | 54 | //关联表视图到滚动条视图 55 | self.tableViewScrollView?.documentView = self.tableView 56 | //将滚动条视图添加到父视图 57 | self.view.addSubview(self.tableViewScrollView!); 58 | 59 | //将导航面板视图添加到父视图 60 | self.view.addSubview(self.dataNavigationView!); 61 | 62 | //表格视图自动布局配置 63 | { 64 | let top = self.tableViewScrollView?.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0) 65 | let bottom = self.tableViewScrollView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -30) 66 | 67 | let left = self.tableViewScrollView?.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0) 68 | let right = self.tableViewScrollView?.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0) 69 | 70 | //激活自动布局约束 71 | NSLayoutConstraint.activate([left!, right!,top!, bottom! ]) 72 | 73 | }(); 74 | 75 | //底部导航视图自动布局配置 76 | { 77 | let top = self.dataNavigationView?.topAnchor.constraint(equalTo: (self.tableViewScrollView?.bottomAnchor)!, constant: 0) 78 | let bottom = self.dataNavigationView?.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0) 79 | 80 | let left = self.dataNavigationView?.leftAnchor.constraint(equalTo: self.view.leftAnchor, constant: 0) 81 | let right = self.dataNavigationView?.rightAnchor.constraint(equalTo: self.view.rightAnchor, constant: 0) 82 | 83 | //激活自动布局约束 84 | NSLayoutConstraint.activate([left!, right!,top!, bottom! ]) 85 | 86 | }() 87 | } 88 | 89 | func tableViewColumnConfig(){ 90 | let items = self.tableColumnItems() 91 | self.tableView?.xx_updateColumnsWithItems(items) 92 | } 93 | 94 | func tableColumnItems() ->[TableColumnItem] { 95 | return [TableColumnItem]() 96 | } 97 | 98 | func tableViewStyleConfig() { 99 | self.tableView?.gridStyleMask = [NSTableView.GridLineStyle.solidHorizontalGridLineMask,NSTableView.GridLineStyle.solidVerticalGridLineMask ] 100 | self.tableView?.usesAlternatingRowBackgroundColors = true 101 | } 102 | 103 | func tableDelegateConfig() { 104 | // subclass override this in subclass 105 | } 106 | 107 | 108 | func registerRowDrag() { 109 | self.tableView?.registerForDraggedTypes([NSPasteboard.PasteboardType(rawValue: kTableViewDragDataTypeName)]) 110 | } 111 | 112 | // MARK: Action 113 | 114 | @IBAction func toolButtonClicked(_ sender: NSButton) { 115 | let actionType = DataNavigationViewButtonActionType(rawValue: sender.tag)! 116 | 117 | switch (actionType) { 118 | 119 | case .add: 120 | self.addNewData() 121 | break 122 | 123 | case .remove: 124 | self.reomoveSelectedData() 125 | break 126 | 127 | case .refresh: 128 | self.pageManager.refreshCurrentPage() 129 | break 130 | 131 | case .first: 132 | self.pageManager.goFirstPage() 133 | break 134 | 135 | case .pre: 136 | self.pageManager.goPrePage() 137 | break 138 | 139 | case .next: 140 | self.pageManager.goNextPage() 141 | break 142 | 143 | case .last: 144 | self.pageManager.goLastPage() 145 | break 146 | } 147 | 148 | } 149 | 150 | func addNewData() { 151 | 152 | } 153 | 154 | func reomoveSelectedData() { 155 | let selectedRow = self.tableView?.selectedRow 156 | //没有行选择,不执行删除操作 157 | if selectedRow! < 0 { 158 | return 159 | } 160 | //开始删除 161 | self.tableView?.beginUpdates() 162 | let indexes = self.tableView?.selectedRowIndexes 163 | //以指定的动画风格执行删除 164 | self.tableView?.removeRows(at: indexes!, withAnimation: NSTableView.AnimationOptions.slideUp) 165 | //完成删除 166 | self.tableView?.endUpdates() 167 | } 168 | 169 | 170 | // MARK: NavigationView Config 171 | 172 | func dataNavigationItemsConfig() -> [DataNavigationItem] { 173 | let insertItem = DataNavigationButtonItem() 174 | insertItem.imageName = NSImage.Name.addTemplate.rawValue 175 | insertItem.tooltips = "insert a row into current table" 176 | insertItem.tag = .add 177 | 178 | let deleteItem = DataNavigationButtonItem() 179 | deleteItem.imageName = NSImage.Name.removeTemplate.rawValue 180 | deleteItem.tooltips = "delete seleted rows form current table" 181 | deleteItem.tag = .remove 182 | 183 | let refreshItem = DataNavigationButtonItem() 184 | refreshItem.imageName = NSImage.Name.refreshTemplate.rawValue 185 | refreshItem.tooltips = "reload table data" 186 | refreshItem.tag = .refresh 187 | 188 | let flexibleItem = DataNavigationFlexibleItem() 189 | 190 | 191 | let firstItem = DataNavigationButtonItem() 192 | firstItem.imageName = kToolbarFirstImageName 193 | firstItem.tooltips = "go first page" 194 | firstItem.tag = .first 195 | 196 | let preItem = DataNavigationButtonItem() 197 | preItem.imageName = kToolbarPreImageName 198 | preItem.tooltips = "go pre page" 199 | preItem.tag = .pre 200 | 201 | let pageLable = DataNavigationTextItem() 202 | pageLable.identifier = kPagesIdentifier 203 | pageLable.title = "0/0" 204 | pageLable.alignment = .center 205 | 206 | 207 | let nextItem = DataNavigationButtonItem() 208 | nextItem.imageName = kToolbarNextImageName 209 | nextItem.tooltips = "go next page" 210 | nextItem.tag = .next 211 | 212 | let lastItem = DataNavigationButtonItem() 213 | lastItem.imageName = kToolbarLastImageName 214 | lastItem.tooltips = "go last page" 215 | lastItem.tag = .last 216 | 217 | 218 | return [insertItem,deleteItem,refreshItem,flexibleItem,firstItem,preItem,pageLable,nextItem,lastItem] 219 | } 220 | 221 | 222 | func tableViewSortColumnsConfig() { 223 | 224 | for tableColumn in (self.tableView?.tableColumns)! { 225 | //升序排序 226 | 227 | //使用系统的排序方法 228 | let sortDescriptor = NSSortDescriptor(key: tableColumn.identifier.rawValue, ascending: true, 229 | selector: #selector(NSNumber.compare(_:))) 230 | 231 | //使用自定义的排序方法 232 | let sortRules = NSSortDescriptor(key: tableColumn.identifier.rawValue, ascending: true, comparator:{ s1,s2 in 233 | let str1 = s1 as! String 234 | let str2 = s2 as! String 235 | if str1 > str2 {return .orderedAscending} 236 | if str1 < str2 {return .orderedDescending} 237 | return .orderedSame 238 | } 239 | ) 240 | 241 | //image列暂不排序 242 | if tableColumn.identifier.rawValue != "image" { 243 | tableColumn.sortDescriptorPrototype = sortDescriptor 244 | } 245 | } 246 | } 247 | 248 | // MARK : Page 249 | 250 | func computePageNumbers () { 251 | return self.pageManager.computePageNumbers() 252 | } 253 | 254 | func updatePageInfo() { 255 | let currentPageIndex = self.pageManager.page 256 | let pageNumbers = self.pageManager.pages 257 | var pageInfo: String? 258 | if pageNumbers > 0 { 259 | pageInfo = "\(currentPageIndex+1)/\(pageNumbers)" 260 | } 261 | else { 262 | pageInfo = "\(currentPageIndex)/\(pageNumbers)" 263 | } 264 | 265 | self.dataNavigationView?.updatePagesLabel(title: pageInfo!) 266 | let info = "\(self.pageManager.total) records" 267 | self.dataNavigationView?.updateInfoLabel(title: info) 268 | } 269 | 270 | 271 | // MARK: ivars 272 | 273 | func tableViewXibScrollView() ->NSScrollView? { 274 | return nil 275 | } 276 | 277 | func tableXibView() ->NSTableView? { 278 | return nil 279 | } 280 | 281 | func dataNavigationXibView() ->DataNavigationView? { 282 | return nil 283 | } 284 | 285 | 286 | lazy var tableView: NSTableView? = { 287 | var tb: NSTableView? = self.tableXibView() 288 | if tb == nil { 289 | tb = NSTableView() 290 | tb?.focusRingType = .none 291 | tb?.autoresizesSubviews = true 292 | } 293 | return tb 294 | }() 295 | 296 | lazy var tableViewScrollView: NSScrollView? = { 297 | 298 | var tbScollView: NSScrollView? = self.tableViewXibScrollView() 299 | if tbScollView == nil { 300 | tbScollView = NSScrollView() 301 | tbScollView?.hasVerticalScroller = false 302 | tbScollView?.hasVerticalScroller = false 303 | tbScollView?.focusRingType = .none 304 | tbScollView?.autohidesScrollers = true 305 | tbScollView?.borderType = .noBorder 306 | tbScollView?.translatesAutoresizingMaskIntoConstraints = false 307 | } 308 | return tbScollView 309 | }() 310 | 311 | lazy var dataNavigationView: DataNavigationView? = { 312 | 313 | var dataNav: DataNavigationView? = self.dataNavigationXibView() 314 | if dataNav == nil { 315 | dataNav = DataNavigationView() 316 | dataNav?.translatesAutoresizingMaskIntoConstraints = false 317 | } 318 | return dataNav 319 | }() 320 | } 321 | 322 | 323 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Controller/TableDataNavigationViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableDataNavigationViewDelegate.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/21. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableDataNavigationViewDelegate: TableDataDelegate { 12 | func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat { 13 | return 24 14 | } 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Controller/XibViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XibViewController.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/21. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class XibViewController: NSViewController { 12 | 13 | override init(nibName nibNameOrNil: NSNib.Name?, bundle nibBundleOrNil: Bundle?) { 14 | if nibNameOrNil == nil { 15 | super.init(nibName: NSNib.Name(rawValue: "XibViewController"), bundle: nibBundleOrNil) 16 | } 17 | else { 18 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 19 | } 20 | } 21 | 22 | required init?(coder: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | override func viewDidLoad() { 27 | super.viewDidLoad() 28 | // Do view setup here. 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Controller/XibViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/DataPageManager/DataPageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataPageManager.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | public protocol PaginatorDelegate: class { 14 | func paginator(id:AnyObject , pageIndex index:Int, pageSize size:Int ) 15 | func totalNumberOfData(_ id:AnyObject) ->Int 16 | } 17 | 18 | class DataPageManager : NSObject { 19 | 20 | weak var delegate: PaginatorDelegate? 21 | var page: Int = 0 22 | var pageSize: Int = 0 23 | var pages: Int = 0 24 | var total: Int = 0 25 | 26 | convenience init(pageSize: Int , delegate paginatorDelegate: PaginatorDelegate? ) { 27 | self.init() 28 | self.pageSize = pageSize 29 | self.delegate = paginatorDelegate 30 | } 31 | 32 | convenience init(pageSize: Int ) { 33 | self.init(pageSize: pageSize , delegate: nil) 34 | } 35 | 36 | func isFirstPage() ->Bool { 37 | return self.page == 0 38 | } 39 | 40 | func isLastPage() ->Bool { 41 | return (self.pages == 0 || self.page == self.pages-1) 42 | } 43 | 44 | func goPage(_ index:Int) { 45 | if self.pages > 0 && index <= self.pages-1 { 46 | self.page = index 47 | 48 | if let paginatorDelegate = self.delegate { 49 | paginatorDelegate.paginator(id: self, pageIndex: index, pageSize: self.pageSize) 50 | } 51 | } 52 | } 53 | 54 | func refreshCurrentPage() { 55 | self.goPage(self.page) 56 | } 57 | 58 | func refreshCurrentPage(complete: (()->Void)? ) { 59 | self.goPage(self.page) 60 | if let callback = complete { 61 | let deadlineTime = DispatchTime.now() + .milliseconds(100) 62 | DispatchQueue.main.asyncAfter(deadline: deadlineTime) { 63 | callback() 64 | } 65 | } 66 | } 67 | 68 | func goNextPage(){ 69 | if !self.isLastPage() { 70 | self.goPage(self.page+1) 71 | } 72 | } 73 | 74 | func goPrePage() { 75 | if !self.isFirstPage() { 76 | self.goPage(self.page-1) 77 | } 78 | } 79 | 80 | func goFirstPage(){ 81 | self.goPage(0) 82 | } 83 | 84 | func goLastPage() { 85 | if self.pages > 1 { 86 | return self.goPage(self.pages-1) 87 | } 88 | } 89 | 90 | func reset() { 91 | self.pages = 0 92 | self.page = 0 93 | } 94 | 95 | func computePageNumbers() { 96 | self.reset() 97 | if let paginatorDelegate = self.delegate { 98 | self.total = paginatorDelegate.totalNumberOfData(self) 99 | if self.pageSize > 0 { 100 | self.pages = Int(ceil( Double(self.total) / Double(self.pageSize))) 101 | } 102 | else{ 103 | self.pages = 0 104 | } 105 | } 106 | } 107 | } 108 | 109 | 110 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Components/DataNavigationViewController/Extension/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSButton+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSButton+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | 13 | extension NSButton { 14 | convenience init(item: TableColumnItem) { 15 | self.init() 16 | self.setButtonType(.switch) 17 | self.bezelStyle = .regularSquare 18 | self.title = "" 19 | self.cell?.isBordered = false 20 | self.identifier = item.identifier.map { NSUserInterfaceItemIdentifier(rawValue: $0) } 21 | } 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSComboBox+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSComboBox+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | 13 | extension NSComboBox { 14 | convenience init(item: TableColumnItem) { 15 | self.init() 16 | self.isEditable = item.editable 17 | self.isBordered = false 18 | self.isBezeled = false 19 | self.bezelStyle = .roundedBezel 20 | self.identifier = item.identifier.map { NSUserInterfaceItemIdentifier(rawValue: $0) } 21 | } 22 | } 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSImageView+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSImageView+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | extension NSImageView { 12 | 13 | convenience init(item: TableColumnItem) { 14 | self.init() 15 | self.identifier = item.identifier.map { NSUserInterfaceItemIdentifier(rawValue: $0) } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSTableColumn+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableColumn+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | extension NSTableColumn { 13 | 14 | static func column(_ item: TableColumnItem) -> NSTableColumn{ 15 | let column = NSTableColumn() 16 | column.identifier = NSUserInterfaceItemIdentifier(rawValue: item.identifier!) 17 | column.width = item.width 18 | column.minWidth = item.minWidth 19 | column.maxWidth = item.maxWidth 20 | column.isEditable = item.editable 21 | column.updateHeaderCellWithItem(item) 22 | return column 23 | } 24 | 25 | func updateHeaderCellWithItem(_ item: TableColumnItem) { 26 | if let headTitle = item.title { 27 | self.headerCell.title = headTitle 28 | } 29 | self.headerCell.alignment = item.headerAlignment 30 | self.headerCell.lineBreakMode = .byTruncatingMiddle 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSTableView+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | private var itemKey = "itemKey" 13 | 14 | extension NSTableView { 15 | var items: [TableColumnItem]? { 16 | get { 17 | if let itemObjs = objc_getAssociatedObject( 18 | self, 19 | &itemKey 20 | ) 21 | { 22 | return itemObjs as? [TableColumnItem] 23 | } 24 | return nil 25 | } 26 | set { 27 | objc_setAssociatedObject(self, 28 | &itemKey, 29 | newValue, 30 | .OBJC_ASSOCIATION_RETAIN) 31 | } 32 | } 33 | 34 | //删除所有的列 35 | func xx_removeAllColumns() { 36 | while self.tableColumns.count > 0 { 37 | let tableColumn = self.tableColumns.last 38 | self.removeTableColumn(tableColumn!) 39 | } 40 | } 41 | 42 | //使用items定义的TableColumnItem数组来创建列 43 | func xx_updateColumnsWithItems(_ items: [TableColumnItem]) { 44 | if items.count <= 0 { 45 | return 46 | } 47 | self.xx_removeAllColumns() 48 | self.items = items 49 | for item in items { 50 | let column = NSTableColumn.column(item) 51 | self.addTableColumn(column) 52 | } 53 | } 54 | 55 | //根据identifier获取列定义 56 | func xx_columnItemWithIdentifier(_ identifier: String) ->TableColumnItem? { 57 | for item in self.items! { 58 | if item.identifier == identifier { 59 | return item 60 | } 61 | } 62 | return nil 63 | } 64 | 65 | //根据index序号获取列定义 66 | func xx_columnItemAtIndex(_ index: Int) ->TableColumnItem? { 67 | if index >= (self.items?.count)! { 68 | return nil 69 | } 70 | return self.items?[index] 71 | } 72 | 73 | 74 | //设置某行某列选中的焦点状态 75 | func xx_setEditFoucusAtColumn(_ columnIndex: Int, atRow rowIndex: Int ) { 76 | if self.numberOfRows <= 0 { 77 | return 78 | } 79 | if rowIndex >= self.numberOfRows { 80 | return 81 | } 82 | 83 | self.xx_setSelectionAtRow(rowIndex) 84 | self.editColumn(columnIndex, row: rowIndex, with: nil, select: true) 85 | } 86 | 87 | //设置某列选中的状态 88 | func xx_setEditFoucusAtColumn(_ columnIndex: Int) { 89 | if self.numberOfRows <= 0 { 90 | return 91 | } 92 | self.xx_setEditFoucusAtColumn(columnIndex,atRow:self.numberOfRows-1) 93 | } 94 | 95 | //设置某行选中的状态 96 | func xx_setSelectionAtRow(_ rowIndex:Int) { 97 | if self.numberOfRows <= 0 { 98 | return 99 | } 100 | 101 | if rowIndex >= self.numberOfRows { 102 | return 103 | } 104 | 105 | let indexSet = IndexSet.init(integer: rowIndex) 106 | self.selectRowIndexes(indexSet, byExtendingSelection: false) 107 | } 108 | 109 | //失去焦点 110 | func xx_setLostEditFoucus() { 111 | let rowIndex = self.selectedRow 112 | if rowIndex < 0 { 113 | return; 114 | } 115 | 116 | for idx in self.selectedRowIndexes { 117 | self.deselectRow(idx) 118 | } 119 | for idx in self.selectedColumnIndexes { 120 | self.deselectColumn(idx) 121 | } 122 | } 123 | } 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Extension/NSTextField+ColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSTextField+ColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/20. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | 12 | 13 | extension NSTextField { 14 | convenience init(item: TableColumnItem) { 15 | self.init() 16 | self.isBezeled = false 17 | self.drawsBackground = false 18 | self.isEditable = item.editable 19 | self.isSelectable = item.editable 20 | self.identifier = item.identifier.map { NSUserInterfaceItemIdentifier(rawValue: $0) } 21 | } 22 | } 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataNavigationViewController/Model/TableColumnItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableColumnItem.swift 3 | // TableViewControllerDemo 4 | // 5 | // Created by iDevFans on 16/8/19. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Cocoa 11 | import AppKit 12 | 13 | public enum TableColumnCellType : Int { 14 | case label 15 | case textField 16 | case comboBox 17 | case checkBox 18 | case imageView 19 | } 20 | 21 | class TableColumnItem: NSObject { 22 | 23 | //表头定义部分 24 | var title: String? //列标题 25 | var identifier: String?//表列Identifier 26 | var headerAlignment: NSTextAlignment = .center //列标题的alignment 27 | var width: CGFloat = 20 //列宽度 28 | var minWidth: CGFloat = 20 //列最小宽度 29 | var maxWidth: CGFloat = 20 //列最大宽度 30 | var editable: Bool = false //文本是否允许编辑 31 | 32 | //下面是表格单元内容部分 33 | var cellType: TableColumnCellType = .textField //表格单元视图的类型 34 | var textColor: NSColor? //文本的Color 35 | var items: [String]? //Combox类型的items数据 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SQLiteApp/Components/DataStoreBO/DataStoreBO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DataStoreBO.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/6. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | let kDefaultDatabaseName = "MacDev.sqlite" 12 | let kDatabaseNode = "database" 13 | let kTableNode = "table" 14 | 15 | class DataStoreBO: NSObject { 16 | 17 | private static let sharedInstance = DataStoreBO() 18 | class var shared: DataStoreBO { 19 | return sharedInstance 20 | } 21 | 22 | lazy var databaseInfo: DatabaseInfo = { 23 | let dbInfo = DatabaseInfo() 24 | dbInfo.dbPath = (MDatabase.shared.dbPath?.path)! 25 | dbInfo.dbName = (MDatabase.shared.dbPath?.lastPathComponent)! 26 | dbInfo.tables = MDatabase.shared.tables()! 27 | return dbInfo 28 | }() 29 | 30 | lazy var defaultDao: DAO = { 31 | let dao = DAO() 32 | return dao 33 | }() 34 | 35 | func openDefaultDB() -> Bool { 36 | return MDatabase.shared.openDBWithName(dbName: kDefaultDatabaseName) 37 | } 38 | 39 | func openDBWithPath(path: String) -> Bool { 40 | self.clear() 41 | let ret = MDatabase.shared.openDBWithPath(dbPath: URL(fileURLWithPath:path)) 42 | self.refreshDBInfo() 43 | self.defaultDao = DAO() 44 | return ret 45 | } 46 | 47 | func clear() { 48 | MDatabase.shared.close() 49 | } 50 | 51 | func refreshTables() { 52 | self.databaseInfo.tables = MDatabase.shared.tables()! 53 | } 54 | 55 | func refreshDBInfo() { 56 | self.databaseInfo.dbPath = (MDatabase.shared.dbPath?.path)! 57 | self.databaseInfo.dbName = (MDatabase.shared.dbPath?.lastPathComponent)! 58 | self.databaseInfo.tables = MDatabase.shared.tables()! 59 | } 60 | 61 | func tableInfoWithName(tableName: String) -> TableInfo { 62 | return self.databaseInfo.tableInfo(withName: tableName)! 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Components/MDatabase/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/DBObject/DAO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DAO.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/8/31. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class DAO: NSObject { 12 | 13 | var tableName = "" /*table name*/ 14 | 15 | var fldList = [String]() /*all coulumn list*/ 16 | 17 | var keyList = [String]() /*primary key coulumn list*/ 18 | 19 | var fldExcludeKeyList = [String]() /*coulumn list */ 20 | 21 | var recordsNum: Int = 0 22 | 23 | weak var queue: FMDatabaseQueue? 24 | 25 | 26 | override required init() { 27 | super.init() 28 | self.queue = MDatabase.shared.queue 29 | } 30 | 31 | func numbersOfRecord() -> Int { 32 | if tableName.characters.count <= 0 { 33 | return 0 34 | } 35 | let sql = "SELECT count(*) as count FROM \(tableName)" 36 | recordsNum = 0 37 | self.queue?.inDatabase({ db in 38 | if let rs = try? db!.executeQuery(sql,values: nil) { 39 | if rs.next() { 40 | self.recordsNum = Int(rs.int(forColumnIndex: 0)) 41 | } 42 | rs.close() 43 | } 44 | }) 45 | 46 | return recordsNum 47 | } 48 | 49 | 50 | func pageNumberWithSize(pageSize: Int) -> Int { 51 | if tableName.characters.count <= 0 { 52 | return 0 53 | } 54 | guard pageSize > 0 else { 55 | print("invalid pageSize") 56 | return 0 57 | } 58 | let sql = "SELECT count(*) as count FROM \(tableName)" 59 | var pageNumber = 0 60 | self.recordsNum = 0 61 | self.queue?.inDatabase({ db in 62 | if let rs = try? db!.executeQuery(sql,values: nil) { 63 | if rs.next() { 64 | self.recordsNum = Int(rs.int(forColumnIndex: 0)) 65 | } 66 | rs.close() 67 | if self.recordsNum > 0 { 68 | pageNumber = Int(ceil(Double(self.recordsNum / pageSize))) 69 | } 70 | } 71 | }) 72 | return pageNumber 73 | } 74 | 75 | func numbersOfRecordWithSQL(sql: String) -> Int { 76 | 77 | var count = 0 78 | 79 | let queryTemp = sql.lowercased() 80 | 81 | let matchedTableName = queryTemp.matchedSQLTableName() 82 | 83 | guard let tableName = matchedTableName else { 84 | return 0 85 | } 86 | 87 | let countSQL = "SELECT COUNT(*) as count FROM \(tableName)" 88 | 89 | self.queue?.inDatabase({ db in 90 | if let rs = try? db!.executeQuery(countSQL,values: nil) { 91 | if rs.next() { 92 | count = Int(rs.int(forColumnIndex: 0)) 93 | } 94 | rs.close() 95 | } 96 | }) 97 | return count 98 | } 99 | 100 | func pageNumberWithSQL(sql: String, pageSize: Int) -> Int { 101 | guard pageSize > 0 else { 102 | print("invalid pageSize") 103 | return 0 104 | } 105 | var pageNumber = 0 106 | self.queue?.inDatabase({ db in 107 | if let rs = try? db!.executeQuery(sql,values: nil) { 108 | var count = 0 109 | if rs.next() { 110 | count = Int(rs.int(forColumnIndex: 0)) 111 | } 112 | rs.close() 113 | if count > 0 { 114 | pageNumber = Int(ceil(Double(count / pageSize))) 115 | } 116 | } 117 | }) 118 | return pageNumber 119 | } 120 | 121 | func insert(model: AnyObject) -> Bool { 122 | let keyCount = keyList.count 123 | if keyCount == 0 { 124 | return false 125 | } 126 | if keyCount > 0 { 127 | let key = keyList[0] 128 | var keyID = (model.value(forKey: key) as! NSNumber).intValue 129 | if keyID == 0 { 130 | keyID = findMaxKey() + 1 131 | model.setValue(keyID, forKey: key) 132 | } 133 | else { 134 | if findBy(model: model) != nil { 135 | print("info:add \(tableName) exsist!\n") 136 | return update(model: model) 137 | } 138 | } 139 | } 140 | var vals = String() 141 | let fieldCount = fldList.count 142 | for i in 0.. Bool { 168 | 169 | if (findBy(model: model) == nil) { 170 | print("info:no exsist update Model!") 171 | return false 172 | } 173 | var wheres = String() 174 | let keyCount = keyList.count 175 | var fieldVals = String() 176 | var args = [Any]() 177 | let fieldCount = fldExcludeKeyList.count 178 | 179 | for i in 0.. Bool { 228 | var wheres = String() 229 | let keyCount = keyList.count 230 | var args = [Any]() 231 | var i = 0 232 | for keyName in keyList { 233 | if i != keyCount - 1 { 234 | wheres += String(format: "%@ = ? AND ", keyName) 235 | } 236 | else { 237 | wheres += String(format: "%@ = ? ", keyName) 238 | } 239 | let value = model.value(forKey: keyName) 240 | args.append(value!) 241 | i = i + 1 242 | } 243 | let sql = "DELETE FROM \(tableName) WHERE \(wheres) " 244 | print("delete sql=%@", sql) 245 | var isOK = false 246 | self.queue?.inDatabase({ db in 247 | isOK = db!.executeUpdate(sql, withArgumentsIn: args) 248 | print("delete lastErrorMessage \(db?.lastErrorMessage())") 249 | }) 250 | return isOK 251 | } 252 | 253 | func removeAll() -> Bool { 254 | let sql = "DELETE FROM \(tableName) " 255 | print("clearAll sql= \(sql)") 256 | return sqlUpdate(sql: sql) 257 | } 258 | 259 | func sqlUpdate(sql: String) -> Bool { 260 | return sqlUpdate(sql: sql, withArgumentsIn: [AnyObject]()) 261 | } 262 | 263 | func sqlUpdate(sql: String, withArgumentsIn args: [AnyObject]?) -> Bool { 264 | var isOK = false 265 | self.queue?.inDatabase({ db in 266 | isOK = db!.executeUpdate(sql, withArgumentsIn: args!) 267 | }) 268 | return isOK 269 | } 270 | 271 | func sqlUpdate(sql: String, withParameterDictionary dics: [NSObject : AnyObject]?) -> Bool { 272 | var isOK = false 273 | self.queue?.inDatabase({ db in 274 | isOK = db!.executeUpdate(sql, withParameterDictionary: dics!) 275 | }) 276 | return isOK 277 | } 278 | 279 | func sqlQuery(sql: String) -> [Any]? { 280 | var modles = [Any]() 281 | self.queue?.inDatabase({ db in 282 | if let rs = try? db!.executeQuery(sql,values: nil) { 283 | while rs.next() { 284 | let jsonData = rs.resultDictionary() 285 | modles.append(jsonData!) 286 | } 287 | rs.close() 288 | } 289 | }) 290 | return modles 291 | } 292 | 293 | func sqlQuery(sql: String, pageIndex: Int, pageSize: Int) -> [Any]? { 294 | let finalSQL = " \(sql) LIMIT \(pageSize) OFFSET \(pageSize * pageIndex) " 295 | return sqlQuery(sql: finalSQL) 296 | } 297 | 298 | func sqlQuery(sql: String, withArgumentsIn args: [Any]) -> [Any]? { 299 | if args.count == 0 { 300 | return sqlQuery(sql: sql) 301 | } 302 | var modles = [Any]() 303 | self.queue?.inDatabase({ db in 304 | if let rs = db!.executeQuery(sql, withArgumentsIn: args) { 305 | while rs.next() { 306 | let jsonData = rs.resultDictionary() 307 | modles.append(jsonData!) 308 | } 309 | rs.close() 310 | } 311 | }) 312 | return modles 313 | } 314 | 315 | func sqlQuery(sql: String, withParameterDictionary dics: [String : Any]) -> [Any]? { 316 | let count = dics.keys.count 317 | if count == 0 { 318 | return sqlQuery(sql:sql) 319 | } 320 | var modles = [Any]() 321 | self.queue?.inDatabase({ db in 322 | if let rs = db!.executeQuery(sql, withParameterDictionary: dics) { 323 | while rs.next() { 324 | let jsonData = rs.resultDictionary() 325 | modles.append(jsonData!) 326 | } 327 | rs.close() 328 | } 329 | }) 330 | return modles 331 | } 332 | 333 | 334 | func findAll() -> [Any]? { 335 | let sql = "SELECT * FROM \(tableName) " 336 | print("findAll sql= \(sql)") 337 | return sqlQuery(sql: sql) 338 | } 339 | 340 | 341 | func findBy(model: AnyObject) -> Any? { 342 | let keyCount = keyList.count 343 | if keyCount <= 0 { 344 | print("table no primary key field!") 345 | return nil 346 | } 347 | var kv = [String : Any]() 348 | for k in keyList { 349 | let v = model.value(forKey: k) 350 | kv[k] = v 351 | } 352 | return findBy(kv: kv) 353 | } 354 | 355 | func findBy(kv: [String : Any]) -> Any? { 356 | var datas = findByAttributes(attributes: kv)! 357 | if datas.count > 0 { 358 | return datas[0] 359 | } 360 | return nil 361 | } 362 | 363 | func findByAttributes(attributes: [String : Any]) -> [Any]? { 364 | var wheres = String() 365 | let keyCount = attributes.keys.count 366 | if keyCount <= 0 { 367 | print("findByAttributes : attribute parameter is null !") 368 | return nil 369 | } 370 | var i = 0 371 | for fName in attributes.keys { 372 | if i != keyCount - 1 { 373 | wheres += String(format: "%@ = :%@ AND ", fName, fName) 374 | } 375 | else { 376 | wheres += String(format: "%@ = :%@ ", fName, fName) 377 | } 378 | i = i + 1 379 | } 380 | let sql = "SELECT * FROM \(tableName) WHERE \(wheres) " 381 | print("findByAttributes sql=\(sql)") 382 | return sqlQuery(sql:sql, withParameterDictionary: attributes) 383 | } 384 | 385 | func findByPage(pageIndex: Int, pageSize: Int) -> [Any]? { 386 | let sql = "SELECT * FROM \(tableName) LIMIT \(pageSize) OFFSET \(pageSize * pageIndex) " 387 | print("findByPage sql=\(sql) pageindex=\(pageIndex)") 388 | return sqlQuery(sql: sql) 389 | } 390 | 391 | 392 | func findMaxKey() -> Int { 393 | let keyCount = keyList.count 394 | if keyCount <= 0 { 395 | print("table no primary key field!") 396 | return 1 397 | } 398 | let sql = "SELECT max(\(keyList[0])) FROM \(tableName) " 399 | 400 | var maxValue = 1 401 | self.queue?.inDatabase({ db in 402 | if let rs = try! db?.executeQuery(sql,values: nil) { 403 | maxValue = Int(rs.int(forColumnIndex: 0)) 404 | rs.close() 405 | } 406 | }) 407 | return maxValue 408 | } 409 | } 410 | 411 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/DBObject/MDAO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDAO.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MDAO: DAO { 12 | 13 | override func sqlQuery(sql: String) -> [Any]? { 14 | var modles = [Any]() 15 | self.queue?.inDatabase({ db in 16 | let rs = try? db!.executeQuery(sql,values: nil) 17 | while rs!.next() { 18 | if let json = rs!.resultDictionary() { 19 | let model = self.createModel(json: json as! [String:Any]) 20 | modles.append(model) 21 | } 22 | } 23 | rs!.close() 24 | }) 25 | return modles 26 | } 27 | 28 | 29 | override func sqlQuery(sql: String, withArgumentsIn args: [Any]) -> [Any]? { 30 | if args.count == 0 { 31 | return self.sqlQuery(sql: sql) 32 | } 33 | var modles = [Any]() 34 | self.queue?.inDatabase({ db in 35 | let rs = db!.executeQuery(sql, withArgumentsIn: args) 36 | while rs!.next() { 37 | if let json = rs!.resultDictionary() { 38 | let model = self.createModel(json: json as! [String:Any]) 39 | modles.append(model) 40 | } 41 | } 42 | rs!.close() 43 | }) 44 | return modles 45 | } 46 | 47 | override func sqlQuery(sql: String, withParameterDictionary dics: [String : Any]) -> [Any]? { 48 | let count = dics.keys.count 49 | if count == 0 { 50 | return self.sqlQuery(sql:sql) 51 | } 52 | var modles = [Any]() 53 | self.queue?.inDatabase({ db in 54 | let rs = db!.executeQuery(sql, withParameterDictionary: dics) 55 | while rs!.next() { 56 | if let json = rs!.resultDictionary() { 57 | let model = self.createModel(json: json as! [String:Any]) 58 | modles.append(model) 59 | } 60 | } 61 | rs!.close() 62 | }) 63 | return modles 64 | } 65 | 66 | func createModel(json: [String:Any])-> MModel { 67 | let className = Bundle.main.infoDictionary!["CFBundleName"] as! String + "." + self.tableName 68 | let aClass = NSClassFromString(className) as! MModel.Type 69 | let model = aClass.init() 70 | model.setValuesForKeys(json) 71 | return model 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/DBObject/MDatabase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDatabase.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MDatabase: NSObject { 12 | var queue: FMDatabaseQueue! 13 | var dbName = "" 14 | var dbPath: URL? 15 | 16 | deinit { 17 | self.close() 18 | } 19 | 20 | private static let sharedInstance = MDatabase() 21 | class var shared: MDatabase { 22 | return sharedInstance 23 | } 24 | 25 | func openDBWithName(dbName: String) -> Bool { 26 | self.dbName = dbName 27 | let dbPath = URL(fileURLWithPath: self.docPath()).appendingPathComponent(dbName) 28 | return self.openDBWithPath(dbPath: dbPath) 29 | } 30 | 31 | func openDBWithPath(dbPath: URL) -> Bool { 32 | let fileManager = FileManager.default 33 | 34 | let success = fileManager.fileExists(atPath: dbPath.path) 35 | if !success { 36 | let resourcePath = Bundle.main.resourcePath! 37 | let defaultDBPath = URL(fileURLWithPath: resourcePath).appendingPathComponent(self.dbName) 38 | 39 | do { 40 | try fileManager.copyItem(at: defaultDBPath, to: dbPath) 41 | } 42 | catch let error { 43 | print("copy path \(dbPath.path) error \(error)") 44 | } 45 | 46 | } 47 | self.dbPath = dbPath 48 | self.queue = FMDatabaseQueue(path: dbPath.path) 49 | if (self.queue == nil) { 50 | print("\n create queue failed!") 51 | return false 52 | } 53 | return true 54 | } 55 | 56 | func close() { 57 | if (self.queue != nil) { 58 | self.queue.close() 59 | self.queue = nil 60 | } 61 | } 62 | 63 | func docPath() -> String { 64 | let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) 65 | return paths[0] 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/DBObject/MModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MModel.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class MModel: NSObject { 12 | 13 | 14 | override required init() { 15 | super.init() 16 | } 17 | 18 | lazy var dao: MDAO = { 19 | print("\(type(of: self))") 20 | print("\(self.className)") 21 | let className = "\(self.className)DAO" 22 | let aClass = NSClassFromString(className) as! MDAO.Type 23 | return aClass.init() 24 | 25 | }() 26 | 27 | func save() { 28 | _ = self.dao.insert(model: self) 29 | } 30 | 31 | func update() { 32 | _ = self.dao.update(model: self) 33 | } 34 | 35 | func delete() { 36 | _ = self.dao.delete(model: self) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/Meta/DatabaseInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DatabaseInfo.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DatabaseInfo: NSObject { 12 | var dbName = "" 13 | var dbPath = "" 14 | var tables = [TableInfo]() 15 | 16 | func tableInfo(withName tableName: String) -> TableInfo? { 17 | for table: TableInfo in self.tables { 18 | if (table.name == tableName) { 19 | return table 20 | } 21 | } 22 | return nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/Meta/FieldInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FieldInfo.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class FieldInfo: NSObject { 12 | var name = "" //字段名称 13 | var type: String = "" //字段数据库类型 14 | var defaultVal = "" //缺省值 15 | var isNULL = false //是否为空 16 | var isKey = false //是否为主键 17 | 18 | //字段数据库类型对应的swift类型 19 | var swiftType: String { 20 | get { 21 | return TypeKit.swiftType(self.type.uppercased())! 22 | } 23 | } 24 | 25 | //数值类型 26 | var isSimpleType: Bool { 27 | get { 28 | return TypeKit.isSimpleType(self.type.uppercased()) 29 | } 30 | } 31 | 32 | //对象类型 33 | var isObjectType: Bool { 34 | get { 35 | return TypeKit.isObjectType(self.type.uppercased()) 36 | } 37 | } 38 | 39 | 40 | 41 | var isBOOL: Bool { 42 | get { 43 | return self.type == "BOOL" 44 | } 45 | } 46 | 47 | 48 | var isINTEGER: Bool { 49 | get { 50 | return self.type == "INT" || self.type == "INTEGER" 51 | } 52 | } 53 | 54 | 55 | var isAutoIncrement: Bool { 56 | get { 57 | return self.type == "INTEGER" && self.isKey == true 58 | } 59 | } 60 | 61 | var isINT: Bool { 62 | get { 63 | return self.type == "INT" 64 | } 65 | } 66 | 67 | var isLONG: Bool { 68 | get { 69 | return self.type == "LONG" 70 | } 71 | } 72 | 73 | var isDOUBLE: Bool { 74 | get { 75 | return self.type == "DOUBLE" 76 | } 77 | } 78 | 79 | var isFLOAT: Bool { 80 | get { 81 | return self.type == "FLOAT" 82 | } 83 | } 84 | 85 | var isTEXT: Bool { 86 | get { 87 | return self.type == "TEXT" 88 | } 89 | } 90 | 91 | var isVARCHAR: Bool { 92 | get { 93 | return self.type.contains("CHAR") 94 | 95 | } 96 | } 97 | 98 | 99 | var isDATETIME: Bool { 100 | get { 101 | return self.type == "DATETIME" 102 | 103 | } 104 | } 105 | 106 | var isNUMERIC: Bool { 107 | get { 108 | return self.type == "NUMERIC" 109 | 110 | } 111 | } 112 | 113 | var isNSString: Bool { 114 | get { 115 | return self.type == "NSSTRING" 116 | 117 | } 118 | } 119 | 120 | 121 | var isBLOB: Bool { 122 | get { 123 | return self.type == "BLOB" 124 | 125 | } 126 | } 127 | 128 | 129 | } 130 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/Meta/MDatabase+Meta.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MDatabase+Meta.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension MDatabase { 12 | 13 | func tables() -> [TableInfo]? { 14 | var tables = [TableInfo]() 15 | self.queue.inDatabase({ db in 16 | let sql = "SELECT * FROM sqlite_master where type = 'table' " 17 | var rs: FMResultSet? 18 | do { 19 | rs = try db!.executeQuery(sql,values: nil) 20 | } 21 | catch let error { 22 | print("error \(error)") 23 | return 24 | } 25 | 26 | while rs!.next() { 27 | let tableName = rs?.string(forColumn: "tbl_name") 28 | let table = TableInfo() 29 | table.name = tableName! 30 | tables.append(table) 31 | } 32 | rs?.close() 33 | 34 | for table: TableInfo in tables { 35 | let tableSQL = " PRAGMA table_info ( \(table.name) ) " 36 | var rs: FMResultSet? 37 | do { 38 | rs = try db!.executeQuery(tableSQL,values: nil) 39 | } 40 | catch let error { 41 | print("error \(error)") 42 | continue 43 | } 44 | 45 | var fields = [FieldInfo]() 46 | var keys = [FieldInfo]() 47 | while rs!.next() { 48 | // NSDictionary *dict = [rs resultDictionary]; 49 | let field = FieldInfo() 50 | let fName = rs?.string(forColumn: "name") 51 | let fType = rs?.string(forColumn: "type") 52 | 53 | let isKey = rs?.bool(forColumn: "pk") 54 | let isNULL = rs?.bool(forColumn: "notnull") 55 | field.name = fName! 56 | field.type = fType! 57 | 58 | if let defaultV = rs?.string(forColumn: "dflt_value") { 59 | field.defaultVal = defaultV 60 | } 61 | 62 | field.isKey = isKey! 63 | field.isNULL = isNULL! 64 | fields.append(field) 65 | if field.isKey { 66 | keys.append(field) 67 | } 68 | } 69 | rs?.close() 70 | table.fields = fields 71 | table.keys = keys 72 | } 73 | }) 74 | return tables 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/Meta/TableInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableInfo.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class TableInfo: NSObject { 12 | /** Table name */ 13 | var name = "" 14 | /** Table Columns */ 15 | var fields = [FieldInfo]() 16 | /** Table Primary Keys */ 17 | var keys = [FieldInfo]() 18 | } 19 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/Meta/TypeKit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TypeKit.swift 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | 13 | class TypeKit { 14 | 15 | static var typeMaps: [String : String] = 16 | [ 17 | "INTEGER": "Int", 18 | "INT": "Int", 19 | "BOOL": "Bool", 20 | "DOUBLE": "Double", 21 | "FLOAT": "Float", 22 | "TEXT": "String", 23 | "VARCHAR": "String", 24 | "DATETIME": "String", 25 | "NUMERIC": "NSNumber", 26 | "BLOB": "Data" 27 | ] 28 | 29 | 30 | static var simpleMaps: [String] = ["BOOL", 31 | "INTEGER", 32 | "NSINTEGER", 33 | "NSUINTEGER", 34 | "LONG", 35 | "INT", 36 | "DOUBLE", 37 | "FLOAT", 38 | "NUMERIC"] 39 | 40 | 41 | class func swiftType(_ sqliteType: String) -> String? { 42 | return typeMaps[sqliteType] 43 | } 44 | 45 | class func itIsSimpleType(_ type: String) -> Bool { 46 | if simpleMaps.contains(type) { 47 | return true 48 | } 49 | return false 50 | } 51 | 52 | 53 | class func isSimpleType(_ type: String) -> Bool { 54 | return itIsSimpleType(type) 55 | } 56 | 57 | class func isObjectType(_ type: String) -> Bool { 58 | return !itIsSimpleType(type) 59 | } 60 | 61 | class func objectType(_ type: String) -> String? { 62 | var objcTypeStr = swiftType(type) 63 | if objcTypeStr == "" { 64 | objcTypeStr = "String" 65 | } 66 | return objcTypeStr 67 | } 68 | 69 | class func dbTypes() -> [String] { 70 | return ["INTEGER", 71 | "BOOL", 72 | "DOUBLE", 73 | "FLOAT", 74 | "TEXT", 75 | "VARCHAR", 76 | "DATETIME", 77 | "NUMERIC", 78 | "BLOB"] 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /SQLiteApp/Components/MDatabase/String+Regex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String+Regex.swift 3 | // LocalizationTools 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | import Foundation 11 | 12 | extension String { 13 | 14 | func matchedSQLTableName() -> String? { 15 | let pattern = ".*\\s+from\\s+(\\w+).*" 16 | let matched = self.matches(pattern: pattern) 17 | if matched.count > 1 { 18 | return matched[1] 19 | } 20 | return nil 21 | } 22 | 23 | func matches(pattern: String!) -> [String] { 24 | 25 | do { 26 | let regex = try NSRegularExpression(pattern: pattern, options: []) 27 | let nsString = self as NSString 28 | let results = regex.matches(in: self, range: NSMakeRange(0, nsString.length)) 29 | 30 | var match = [String]() 31 | for result in results { 32 | for i in 0.. () 13 | typealias DragFileCallbackBlock = ( _ path: String) -> () 14 | 15 | class TreeViewDataDelegate: NSObject { 16 | 17 | weak var owner: NSOutlineView? 18 | 19 | var treeNodes: TreeNodeModel = TreeNodeModel() 20 | 21 | var selectionChangedCallback: TreeNodeSelectChangedCallback? 22 | var dragFileCallBack: DragFileCallbackBlock? 23 | 24 | func setData(data: TreeNodeModel) { 25 | self.clearAll() 26 | self.treeNodes.childNodes.append(data) 27 | } 28 | 29 | func append(data: TreeNodeModel) { 30 | self.treeNodes.childNodes.append(data) 31 | } 32 | 33 | func clearAll() { 34 | self.treeNodes.childNodes.removeAll() 35 | } 36 | 37 | func deleteData(data: AnyObject) { 38 | if data is IndexSet { 39 | let indexSet = data as! IndexSet 40 | let mutuDatas = NSMutableArray(array: self.treeNodes.childNodes) 41 | mutuDatas.removeObjects(at: indexSet) 42 | self.treeNodes.childNodes.removeAll() 43 | for obj in mutuDatas { 44 | self.treeNodes.childNodes.append(obj as! TreeNodeModel) 45 | } 46 | } 47 | else if data is [TreeNodeModel] { 48 | let datas = data as! [TreeNodeModel] 49 | for obj in datas { 50 | let index = self.treeNodes.childNodes.index(of: obj) 51 | if index != nil { 52 | self.treeNodes.childNodes.remove(at: index!) 53 | } 54 | } 55 | } 56 | else { 57 | let obj = data as! TreeNodeModel 58 | let index = self.treeNodes.childNodes.index(of: obj) 59 | if index != nil { 60 | self.treeNodes.childNodes.remove(at: index!) 61 | } 62 | } 63 | } 64 | 65 | func indexOf(item: TreeNodeModel) ->Int? { 66 | return self.treeNodes.childNodes.index(of: item) 67 | } 68 | 69 | func itemOf(row: Int) -> TreeNodeModel? { 70 | let count = self.treeNodes.childNodes.count 71 | if count == 0 || row >= count { 72 | return nil 73 | } 74 | return self.treeNodes.childNodes[row] 75 | } 76 | 77 | func itemsAt(indexSet: IndexSet) -> [TreeNodeModel] { 78 | let mutuDatas = NSMutableArray(array: self.treeNodes.childNodes) 79 | let items = mutuDatas.objects(at: indexSet) as! [TreeNodeModel] 80 | return items 81 | } 82 | } 83 | 84 | extension TreeViewDataDelegate: NSOutlineViewDelegate { 85 | 86 | func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { 87 | 88 | let view = outlineView.makeView(withIdentifier: (tableColumn?.identifier)!, owner: self) 89 | let subviews = view?.subviews 90 | //let imageView = subviews?[0] as! NSImageView 91 | 92 | let field = subviews?[1] as! NSTextField 93 | let model = item as! TreeNodeModel 94 | 95 | field.stringValue = model.name 96 | return view 97 | } 98 | 99 | func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { 100 | return 40 101 | } 102 | 103 | func outlineViewSelectionDidChange(_ notification: Notification) { 104 | 105 | let treeView = notification.object as! NSOutlineView 106 | let row = treeView.selectedRow 107 | 108 | if let item = treeView.item(atRow: row) { 109 | 110 | let pItem = treeView.parent(forItem: item) 111 | 112 | if let callback = self.selectionChangedCallback { 113 | 114 | callback(item as AnyObject, pItem as AnyObject?) 115 | } 116 | } 117 | } 118 | } 119 | 120 | 121 | extension TreeViewDataDelegate: NSOutlineViewDataSource { 122 | 123 | func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { 124 | 125 | let rootNode:TreeNodeModel 126 | 127 | if item != nil { 128 | rootNode = item as! TreeNodeModel 129 | } 130 | else { 131 | rootNode = self.treeNodes 132 | } 133 | return rootNode.childNodes.count 134 | } 135 | 136 | func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { 137 | 138 | let rootNode:TreeNodeModel 139 | 140 | if item != nil { 141 | rootNode = item as! TreeNodeModel 142 | } 143 | else { 144 | rootNode = self.treeNodes 145 | } 146 | return rootNode.childNodes[index] 147 | } 148 | 149 | func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { 150 | let rootNode:TreeNodeModel = item as! TreeNodeModel 151 | return rootNode.childNodes.count > 0 152 | } 153 | 154 | 155 | func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: Any?, proposedChildIndex index: Int) -> NSDragOperation { 156 | return .every 157 | } 158 | 159 | 160 | func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: Any?, childIndex index: Int) -> Bool { 161 | let pboard = info.draggingPasteboard() 162 | self.handleFileBasedDrops(pboard) 163 | return true 164 | } 165 | 166 | func handleFileBasedDrops(_ pboard: NSPasteboard ) { 167 | let fileURLType = NSPasteboard.PasteboardType.backwardsCompatibleFileURL 168 | if (pboard.types?.contains(fileURLType))! { 169 | var filePath: String? 170 | if #available(OSX 10.13, *) { 171 | if let utTypeFilePath = pboard.propertyList(forType:fileURLType) as? URL { 172 | filePath = utTypeFilePath.path 173 | } 174 | } 175 | else { 176 | // UTType 类型的文件路径,需要转换成标准路径 177 | if let utTypeFilePath = pboard.string(forType:fileURLType) { 178 | filePath = URL(fileURLWithPath: utTypeFilePath).standardized.path 179 | } 180 | } 181 | //代理通知 182 | if let dragFileCallBack = self.dragFileCallBack, let filePath = filePath { 183 | dragFileCallBack(filePath) 184 | } 185 | } 186 | } 187 | 188 | } 189 | 190 | 191 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Controllers/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Browse/TableBrowseViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableBrowseViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableBrowseViewController: TableDataNavigationViewController { 12 | 13 | lazy var dataDelegate: TableDataDelegate = { 14 | let delegate = TableBrowseViewDelegate() 15 | return delegate 16 | }() 17 | 18 | var tableName: String = "" 19 | 20 | var datas: Array = [Dictionary]() 21 | 22 | lazy var queue: DispatchQueue = { 23 | let queue = DispatchQueue(label: "macdec.io.BrowseView") 24 | return queue 25 | }() 26 | 27 | 28 | deinit { 29 | unRegisterMultiDelegate() 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | self.tableDelegateConfig() 35 | self.registerMultiDelegate() 36 | 37 | self.contextMenuConfig() 38 | 39 | //计算分页数据 40 | self.pageManager.delegate = self 41 | self.pageManager.pageSize = 10; 42 | 43 | } 44 | 45 | 46 | override func viewDidAppear() { 47 | super.viewDidAppear() 48 | self.view.window?.center() 49 | print("viewDidLayout") 50 | } 51 | 52 | 53 | override func tableDelegateConfig() { 54 | self.tableView?.delegate = self.dataDelegate 55 | self.tableView?.dataSource = self.dataDelegate 56 | self.dataDelegate.owner = self.tableView 57 | 58 | self.dataDelegate.rowObjectValueChangedCallback = { 59 | obj , oldObj , row , fieldName in 60 | if row >= self.datas.count { 61 | return 62 | } 63 | self.datas[row] = obj as! Dictionary 64 | } 65 | } 66 | 67 | 68 | 69 | 70 | func tableViewUpdateColumns(fields: [FieldInfo]) { 71 | if fields.count <= 0 { 72 | return 73 | } 74 | var tableViewColumns = [TableColumnItem]() 75 | for field: FieldInfo in fields { 76 | let col = TableColumnItem() 77 | col.title = field.name 78 | col.identifier = field.name 79 | col.width = 120 80 | col.minWidth = 120 81 | col.maxWidth = 120 82 | col.editable = true 83 | col.headerAlignment = .center 84 | col.cellType = .textField 85 | tableViewColumns.append(col) 86 | } 87 | self.tableView?.xx_updateColumnsWithItems(tableViewColumns) 88 | } 89 | 90 | //MARK: Table Context Menu 91 | 92 | lazy var tableCellMenu: NSMenu = { 93 | let tableCellMenu = NSMenu() 94 | var item = NSMenuItem() 95 | item.title = "Copy as JSON" 96 | tableCellMenu.addItem(item) 97 | item = NSMenuItem() 98 | item.title = "Copy as XML" 99 | tableCellMenu.addItem(item) 100 | return tableCellMenu 101 | 102 | }() 103 | 104 | func contextMenuConfig() { 105 | //关联菜单到tableView 106 | self.tableView?.menu = self.tableCellMenu 107 | let menus = self.tableCellMenu.items 108 | var opIndex = 0 109 | for item: NSMenuItem in menus { 110 | item.target = self 111 | item.tag = opIndex 112 | item.action = #selector(self.tableCellMenuItemClick) 113 | opIndex += 1 114 | } 115 | } 116 | 117 | 118 | 119 | @IBAction func tableCellMenuItemClick(sender: AnyObject) { 120 | let item = sender 121 | let index = item.tag 122 | let selectedRow = self.tableView?.selectedRow 123 | if selectedRow! < 0 { 124 | return 125 | } 126 | var data = self.dataDelegate.itemOf(row: selectedRow!) as! [String:AnyObject] 127 | if index == 0 { 128 | let json = data.jsonString() 129 | NSPasteboard.copyString(str: json!, owner: self) 130 | } 131 | if index == 1 { 132 | let table = DataStoreBO.shared.tableInfoWithName(tableName: self.tableName) 133 | let fields = table.fields 134 | var strs = String() 135 | for field: FieldInfo in fields { 136 | let val = data[field.name] 137 | let colStr = "\(val!)" 138 | strs += colStr 139 | strs += "\n" 140 | } 141 | NSPasteboard.copyString(str: strs, owner: self) 142 | } 143 | } 144 | 145 | } 146 | 147 | extension TableBrowseViewController: TableListSelectionStateDelegate { 148 | 149 | func registerMultiDelegate() { 150 | TableListStateManager.shared.addMultiDelegate(delegate: self, delegateQueue: nil) 151 | } 152 | 153 | func unRegisterMultiDelegate() { 154 | TableListStateManager.shared.removeMultiDelegate(delegate: self) 155 | } 156 | 157 | func selectedTableChanged(sender: AnyObject, tableName: String) { 158 | print("Browse selectedTableChanged \(tableName)") 159 | 160 | let table = DataStoreBO.shared.tableInfoWithName(tableName: tableName) 161 | self.tableName = tableName 162 | let fields = table.fields 163 | DispatchQueue.main.async { 164 | self.tableViewUpdateColumns(fields: fields) 165 | self.tableViewSortColumnsConfig() 166 | //计算分页数据 167 | self.pageManager.computePageNumbers() 168 | //导航到第一页 169 | self.pageManager.goFirstPage() 170 | //更新导航面板分页提示信息 171 | self.updatePageInfo() 172 | 173 | } 174 | } 175 | } 176 | 177 | 178 | extension TableBrowseViewController: PaginatorDelegate { 179 | 180 | func paginator(id:AnyObject , pageIndex index:Int, pageSize size:Int ) { 181 | 182 | DispatchQueue.back.async { 183 | let sql = "select * from \(self.tableName)" 184 | let datas = DataStoreBO.shared.defaultDao.sqlQuery(sql: sql, pageIndex: index, pageSize: size) 185 | self.dataDelegate.setData(data: datas as AnyObject?) 186 | DispatchQueue.main.async { 187 | self.tableView?.reloadData() 188 | self.updatePageInfo() 189 | } 190 | } 191 | } 192 | 193 | func totalNumberOfData(_ id:AnyObject) ->Int { 194 | 195 | let sql = "select * from \(self.tableName)" 196 | return DataStoreBO.shared.defaultDao.numbersOfRecordWithSQL(sql: sql) 197 | } 198 | } 199 | 200 | 201 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Browse/TableBrowseViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableBrowseViewDelegate.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/7. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableBrowseViewDelegate: TableDataDelegate { 12 | 13 | override func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: AnyObject) -> CGFloat { 14 | return 24 15 | } 16 | } 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Main/AppMainSplitViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppMainSplitViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AppMainSplitViewController: NSSplitViewController { 12 | lazy var tableListViewController: TableListViewController = { 13 | let vc = TableListViewController() 14 | return vc 15 | }() 16 | 17 | lazy var appMainTabViewController: AppMainTabViewController = { 18 | let vc = AppMainTabViewController() 19 | return vc 20 | }() 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | self.setUpControllers() 25 | self.configLayout() 26 | } 27 | 28 | func setUpControllers() { 29 | self.addChildViewController(self.tableListViewController) 30 | self.addChildViewController(self.appMainTabViewController) 31 | } 32 | 33 | func configLayout() { 34 | 35 | //设置左边视图的宽度最小100,最大300 36 | self.tableListViewController.view.width >= 100 37 | self.tableListViewController.view.width <= 260 38 | 39 | //设置右边视图的宽度最小300,最大2000 40 | self.appMainTabViewController.view.width >= 300 41 | self.appMainTabViewController.view.width <= 2000 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Main/AppMainSplitViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Main/AppMainTabViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppMainTabViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class AppMainTabViewController: NSTabViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | self.addChildViewControllers() 16 | // Do view setup here. 17 | 18 | } 19 | 20 | func addChildViewControllers() { 21 | 22 | let dataViewController = TableBrowseViewController() 23 | dataViewController.title = "Browse" 24 | 25 | let schemaViewController = TableSchemaViewController() 26 | schemaViewController.title = "Schema" 27 | 28 | let nibName = NSNib.Name("TableQueryViewController") 29 | let queryViewController = TableQueryViewController(nibName: nibName, bundle: nil) 30 | queryViewController.title = "Query" 31 | 32 | self.addChildViewController(dataViewController) 33 | self.addChildViewController(schemaViewController) 34 | self.addChildViewController(queryViewController) 35 | } 36 | 37 | 38 | override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) { 39 | super.tabView(tabView, didSelect: tabViewItem) 40 | print("didSelectTabViewItem \(tabViewItem!)") 41 | 42 | TableListStateManager.shared.multiDelegate.selectedTableChanged() 43 | 44 | } 45 | 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Main/AppMainWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppMainWindowController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | let kHomeURL = "http://www.macdev.io" 12 | 13 | class AppMainWindowController: NSWindowController { 14 | 15 | lazy var splitViewController: AppMainSplitViewController = { 16 | let vc = AppMainSplitViewController() 17 | return vc 18 | }() 19 | 20 | var openFileDlg = NSOpenPanel() 21 | 22 | override func windowDidLoad() { 23 | super.windowDidLoad() 24 | self.configWindowStyle() 25 | self.setWindowTitleImage() 26 | self.contentViewController = self.splitViewController 27 | } 28 | 29 | func configWindowStyle() { 30 | //Toolbar 跟titleBar 融合在一起显示 31 | self.window?.titleVisibility = .hidden; 32 | //透明化 33 | //self.window?.titlebarAppearsTransparent = true 34 | //设置背景颜色 35 | //self.window?.backgroundColor = NSColor.red 36 | } 37 | 38 | override var windowNibName: NSNib.Name? { 39 | return NSNib.Name("AppMainWindowController") 40 | } 41 | 42 | func setWindowTitleImage(){ 43 | self.window?.representedURL = URL(string:"WindowTitle") 44 | self.window?.title = "SQLiteApp" 45 | let image = NSImage(named: NSImage.Name(rawValue: "windowIcon")) 46 | self.window?.standardWindowButton(.documentIconButton)?.image = image 47 | } 48 | 49 | //MARK: Toolbar Action 50 | 51 | @IBAction func openDBActionClicked(_ sender: AnyObject) { 52 | 53 | openFileDlg.canChooseFiles = true 54 | openFileDlg.canChooseDirectories = false 55 | openFileDlg.allowsMultipleSelection = false 56 | openFileDlg.allowedFileTypes = ["sqlite"] 57 | 58 | openFileDlg.begin(completionHandler: { [weak self] result in 59 | 60 | if(result.rawValue == NSFileHandlingPanelOKButton){ 61 | 62 | let fileURLs = self?.openFileDlg.urls 63 | 64 | for url: URL in fileURLs! { 65 | 66 | NotificationCenter.default.post(name:Notification.Name.onOpenDBFile, object: url.path) 67 | 68 | break 69 | } 70 | } 71 | }) 72 | } 73 | 74 | 75 | @IBAction func closeDBActionClicked(_ sender: AnyObject) { 76 | 77 | let alert = NSAlert() 78 | //增加OK按钮 79 | alert.addButton(withTitle: "Ok") 80 | 81 | //增加Cancel按钮 82 | alert.addButton(withTitle: "Cancel") 83 | 84 | //提示的标题 85 | alert.messageText = "Confirm" 86 | //提示的详细内容 87 | alert.informativeText = "Close Database?" 88 | //设置告警风格 89 | alert.alertStyle = .informational 90 | alert.beginSheetModal(for: self.window!, completionHandler: { returnCode in 91 | //当有多个按钮是 可以通过returnCode区分判断 92 | if returnCode == NSApplication.ModalResponse.alertFirstButtonReturn { 93 | 94 | NotificationCenter.default.post(name:Notification.Name.onCloseDBFile, object: nil) 95 | 96 | } 97 | } 98 | ) 99 | } 100 | 101 | @IBAction func homeToolBarClicked(_ sender: AnyObject) { 102 | 103 | let url = URL(string: kHomeURL) 104 | 105 | if !NSWorkspace.shared.open(url!) { 106 | print("open failed!") 107 | } 108 | } 109 | } 110 | 111 | extension Notification.Name { 112 | //定义消息通知名称 113 | static let onOpenDBFile = Notification.Name("on-open-file") 114 | static let onCloseDBFile = Notification.Name("on-close-file") 115 | } 116 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Preferences/PreferencesViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/8. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PreferencesViewController: NSTabViewController { 12 | 13 | lazy var originalSizes = [String : NSSize]() 14 | 15 | // MARK: - NSTabViewDelegate 16 | 17 | override func tabView(_ tabView: NSTabView, willSelect tabViewItem: NSTabViewItem?) { 18 | super.tabView(tabView, willSelect: tabViewItem) 19 | 20 | _ = tabView.selectedTabViewItem 21 | let originalSize = self.originalSizes[tabViewItem!.label] 22 | if (originalSize == nil) { 23 | self.originalSizes[tabViewItem!.label] = (tabViewItem!.view?.frame.size)! 24 | } 25 | } 26 | 27 | override func tabView(_ tabView: NSTabView, didSelect tabViewItem: NSTabViewItem?) { 28 | super.tabView(tabView, didSelect: tabViewItem) 29 | 30 | let window = self.view.window 31 | if (window != nil) { 32 | window?.title = tabViewItem!.label 33 | let size = (self.originalSizes[tabViewItem!.label])! 34 | let contentFrame = (window?.frameRect(forContentRect: NSMakeRect(0.0, 0.0, size.width, size.height)))! 35 | var frame = (window?.frame)! 36 | frame.origin.y = frame.origin.y + (frame.size.height - contentFrame.size.height) 37 | frame.size.height = contentFrame.size.height; 38 | frame.size.width = contentFrame.size.width; 39 | window?.setFrame(frame, display: false, animate: true) 40 | } 41 | 42 | // tabViewItem!.view?.hidden = false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Preferences/PreferencesWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PreferencesWindowController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/8. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class PreferencesWindowController: NSWindowController { 12 | 13 | override func windowDidLoad() { 14 | super.windowDidLoad() 15 | 16 | self.window?.center() 17 | 18 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Query/TableQueryViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableQueryViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableQueryViewController: TableDataNavigationViewController { 12 | 13 | @IBOutlet weak var queryTableXibView: NSTableView! 14 | 15 | @IBOutlet weak var queryTableScrollXibView: NSScrollView! 16 | 17 | @IBOutlet weak var queryNavigationXibView: DataNavigationView! 18 | 19 | @IBOutlet var queryTextView: NSTextView! 20 | 21 | var sql = "" 22 | 23 | lazy var dataDelegate: TableDataDelegate = { 24 | let delegate = TableDataDelegate() 25 | return delegate 26 | }() 27 | 28 | override func viewDidLoad() { 29 | super.viewDidLoad() 30 | //计算分页数据 31 | self.pageManager.delegate = self 32 | self.pageManager.pageSize = 10; 33 | 34 | self.view.wantsLayer = true 35 | self.view.layer?.backgroundColor = NSColor.white.cgColor 36 | } 37 | 38 | 39 | override func tableDelegateConfig() { 40 | self.tableView?.delegate = self.dataDelegate 41 | self.tableView?.dataSource = self.dataDelegate 42 | self.dataDelegate.owner = self.tableView 43 | } 44 | 45 | 46 | // MARK: Action 47 | 48 | @IBAction func clearSQLAction(_ sender: AnyObject) { 49 | self.queryTextView.string = "" 50 | } 51 | @IBAction func runSQLAction(_ sender: AnyObject) { 52 | 53 | print("self.queryTextView.string \(self.queryTextView.string)") 54 | var sql = self.queryTextView.string 55 | if sql.characters.count <= 0 { 56 | return 57 | } 58 | self.sql = sql 59 | let datas = DataStoreBO.shared.defaultDao.sqlQuery(sql: sql, pageIndex: 0, pageSize: 1) 60 | 61 | if (datas?.count)! > 0 { 62 | let data = datas![0] as! [String:AnyObject] 63 | let fieldNames = Array(data.keys) 64 | self.tableViewUpdateColumnWithNames(fieldNames: fieldNames) 65 | } 66 | //计算分页数据 67 | self.pageManager.computePageNumbers() 68 | //导航到第一页 69 | self.pageManager.goFirstPage() 70 | //更新导航面板分页提示信息 71 | self.updatePageInfo() 72 | } 73 | 74 | 75 | 76 | func tableViewUpdateColumnWithNames(fieldNames: [String]) { 77 | if fieldNames.count <= 0 { 78 | return 79 | } 80 | var tableViewColumns = [TableColumnItem]() /* capacity: fieldNames.count */ 81 | for name: String in fieldNames { 82 | let col = TableColumnItem() 83 | col.title = name 84 | col.identifier = name 85 | col.width = 120 86 | col.minWidth = 120 87 | col.maxWidth = 120 88 | col.editable = true 89 | col.headerAlignment = .left 90 | col.cellType = .textField 91 | tableViewColumns.append(col) 92 | } 93 | self.tableView?.xx_updateColumnsWithItems(tableViewColumns) 94 | } 95 | 96 | // MARK: ivars 97 | 98 | override func tableViewXibScrollView() ->NSScrollView? { 99 | return self.queryTableScrollXibView 100 | } 101 | 102 | override func tableXibView() ->NSTableView? { 103 | return self.queryTableXibView 104 | } 105 | 106 | 107 | override func dataNavigationXibView() ->DataNavigationView? { 108 | return self.queryNavigationXibView 109 | } 110 | 111 | 112 | } 113 | 114 | 115 | extension TableQueryViewController: PaginatorDelegate { 116 | 117 | func paginator(id:AnyObject , pageIndex index:Int, pageSize size:Int ) { 118 | 119 | DispatchQueue.back.async { 120 | let datas = DataStoreBO.shared.defaultDao.sqlQuery(sql: self.sql, pageIndex: index, pageSize: size) 121 | self.dataDelegate.setData(data: datas as AnyObject?) 122 | DispatchQueue.main.async { 123 | self.tableView?.reloadData() 124 | self.updatePageInfo() 125 | } 126 | } 127 | } 128 | 129 | func totalNumberOfData(_ id:AnyObject) ->Int { 130 | return DataStoreBO.shared.defaultDao.numbersOfRecordWithSQL(sql: self.sql) 131 | } 132 | } 133 | 134 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Query/TableQueryViewController1.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/Schema/TableSchemaViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableSchemaViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableSchemaViewController: TableDataNavigationViewController { 12 | 13 | lazy var dataDelegate: TableDataDelegate = { 14 | let delegate = TableDataDelegate() 15 | return delegate 16 | }() 17 | 18 | var tableName: String = "" 19 | 20 | deinit { 21 | unRegisterMultiDelegate() 22 | } 23 | 24 | override func viewDidLoad() { 25 | super.viewDidLoad() 26 | self.registerMultiDelegate() 27 | self.tableDelegateConfig() 28 | self.tableView?.xx_updateColumnsWithItems(self.tableColumnItems()) 29 | } 30 | 31 | 32 | func fetchData() { 33 | 34 | DispatchQueue.back.async { 35 | let table = DataStoreBO.shared.tableInfoWithName(tableName: self.tableName) 36 | self.dataDelegate.setData(data: table.fields as AnyObject?) 37 | DispatchQueue.main.async { 38 | self.tableView?.reloadData() 39 | } 40 | } 41 | 42 | } 43 | 44 | 45 | override func tableColumnItems() -> [TableColumnItem] { 46 | 47 | let col1 = TableColumnItem() 48 | col1.title = "Name" 49 | col1.identifier = "name" 50 | col1.width = 120 51 | col1.minWidth = 120 52 | col1.maxWidth = 120 53 | col1.editable = true 54 | col1.textColor = NSColor.highlightColor 55 | col1.headerAlignment = .left 56 | col1.cellType = .textField 57 | 58 | let col2 = TableColumnItem() 59 | col2.title = "Type" 60 | col2.identifier = "type" 61 | col2.width = 100 62 | col2.minWidth = 100 63 | col2.maxWidth = 100 64 | col2.editable = true 65 | col2.textColor = NSColor.highlightColor 66 | col2.headerAlignment = .left 67 | col2.cellType = .comboBox 68 | col2.items = TypeKit.dbTypes() 69 | 70 | let col3 = TableColumnItem() 71 | col3.title = "NULL" 72 | col3.identifier = "isNULL" 73 | col3.width = 100 74 | col3.minWidth = 100 75 | col3.maxWidth = 100 76 | col3.editable = true 77 | col3.textColor = NSColor.highlightColor 78 | col3.headerAlignment = .left 79 | col3.cellType = .checkBox 80 | 81 | let col4 = TableColumnItem() 82 | col4.title = "Default Val" 83 | col4.identifier = "defaultVal" 84 | col4.width = 100 85 | col4.minWidth = 100 86 | col4.maxWidth = 100 87 | col4.editable = true 88 | col4.textColor = NSColor.highlightColor 89 | col4.headerAlignment = .left 90 | col4.cellType = .textField 91 | 92 | let col5 = TableColumnItem() 93 | col5.title = "Primary Key" 94 | col5.identifier = "isKey" 95 | col5.width = 100 96 | col5.minWidth = 100 97 | col5.maxWidth = 100 98 | col5.editable = true 99 | col5.textColor = NSColor.highlightColor 100 | col5.headerAlignment = .left 101 | col5.cellType = .checkBox 102 | 103 | return [ //col0, 104 | col1, col2, col3, col4, col5] 105 | } 106 | 107 | 108 | 109 | override func tableDelegateConfig() { 110 | self.tableView?.delegate = self.dataDelegate 111 | self.tableView?.dataSource = self.dataDelegate 112 | self.dataDelegate.owner = self.tableView 113 | } 114 | 115 | override func dataNavigationItemsConfig() -> [DataNavigationItem] { 116 | let insertItem = DataNavigationButtonItem() 117 | insertItem.imageName = NSImage.Name.addTemplate.rawValue 118 | insertItem.tooltips = "insert a row into current table" 119 | insertItem.tag = .add 120 | 121 | let deleteItem = DataNavigationButtonItem() 122 | deleteItem.imageName = NSImage.Name.removeTemplate.rawValue 123 | deleteItem.tooltips = "delete seleted rows form current table" 124 | deleteItem.tag = .remove 125 | 126 | let refreshItem = DataNavigationButtonItem() 127 | refreshItem.imageName = NSImage.Name.refreshTemplate.rawValue 128 | refreshItem.tooltips = "reload table data" 129 | refreshItem.tag = .refresh 130 | 131 | return [insertItem,deleteItem,refreshItem] 132 | } 133 | } 134 | 135 | 136 | extension TableSchemaViewController: TableListSelectionStateDelegate { 137 | 138 | func registerMultiDelegate() { 139 | TableListStateManager.shared.addMultiDelegate(delegate: self, delegateQueue: nil) 140 | } 141 | 142 | func unRegisterMultiDelegate() { 143 | TableListStateManager.shared.removeMultiDelegate(delegate: self) 144 | } 145 | 146 | func selectedTableChanged(sender: AnyObject, tableName: String) { 147 | print("Schema selectedTableChanged \(tableName)") 148 | self.tableName = tableName 149 | self.fetchData() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/TableList/TableListView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableListView.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/6. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableListView: NSOutlineView { 12 | 13 | weak var tableNodeMenu: NSMenu? 14 | 15 | weak var dataBaseNodeMenu: NSMenu? 16 | 17 | override func menu(for event: NSEvent) -> NSMenu? { 18 | let pt = self.convert(event.locationInWindow, from: nil) 19 | let row = self.row(at: pt) 20 | if row >= 0 { 21 | let item = self.item(atRow: row) as! TreeNodeModel 22 | let type = item.type 23 | if (type == kTableNode) { 24 | return self.tableNodeMenu 25 | } 26 | if (type == kDatabaseNode) { 27 | return self.dataBaseNodeMenu 28 | } 29 | } 30 | return super.menu(for: event)! 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/TableList/TableListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableListViewController.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/4. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | 12 | let kSQLiteDBSavePath = "SQLiteDBSavePath" 13 | 14 | class TableListViewController: NSViewController { 15 | 16 | @IBOutlet var tableNodeMenu: NSMenu! 17 | 18 | @IBOutlet var dataBaseNodeMenu: NSMenu! 19 | 20 | @IBOutlet weak var treeView: TableListView! 21 | 22 | lazy var treeViewDelegate: TableListViewDelegate = { 23 | let delegate = TableListViewDelegate() 24 | return delegate 25 | }() 26 | 27 | lazy var queue: DispatchQueue = { 28 | let queue = DispatchQueue(label: "macdec.io.TableListView") 29 | return queue 30 | }() 31 | 32 | deinit { 33 | unRegisterMultiDelegate() 34 | } 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | 39 | 40 | //先判断是否存在上次打开过的数据库路径 41 | if let dbPath = UserDefaults.standard.object(forKey: kSQLiteDBSavePath) as? String { 42 | if !DataStoreBO.shared.openDBWithPath(path: dbPath) { 43 | print("Open Db \(dbPath) Failed!") 44 | return 45 | } 46 | } 47 | else { 48 | //没有上次的路径则打开默认数据库 49 | if !DataStoreBO.shared.openDefaultDB() { 50 | NSLog("Open Default Database Failed!") 51 | return 52 | } 53 | } 54 | 55 | self.treeViewStyleConfig() 56 | self.registerFilesDrag() 57 | self.registerMultiDelegate() 58 | self.registerNotification() 59 | self.treeViewDelegateConfig() 60 | self.treeViewMenuConfig() 61 | 62 | //开始获取数据中的表信息 63 | self.fetchData() 64 | } 65 | 66 | 67 | func treeViewStyleConfig() { 68 | self.treeView.allowsMultipleSelection = true 69 | let color = NSColor(hex:0xd7dde5) 70 | self.treeView.backgroundColor = color 71 | } 72 | 73 | func registerFilesDrag() { 74 | self.treeView.registerForDraggedTypes([NSPasteboard.PasteboardType.backwardsCompatibleFileURL]) 75 | } 76 | 77 | func treeViewDelegateConfig() { 78 | self.treeView.delegate = self.treeViewDelegate 79 | self.treeView.dataSource = self.treeViewDelegate 80 | self.treeViewDelegate.owner = self.treeView 81 | self.treeViewDelegate.selectionChangedCallback = {(item: AnyObject, parentItem: AnyObject?) -> Void in 82 | let treeNode = item as! TreeNodeModel 83 | if treeNode.type == kDatabaseNode { 84 | return 85 | } 86 | let tableName = treeNode.name 87 | 88 | TableListStateManager.shared.multiDelegate.selectedTableChanged(tableName: tableName) 89 | } 90 | 91 | self.treeViewDelegate.dragFileCallBack = { [weak self] (path: String) -> Void in 92 | self!.openDatabaseWithPath(path: path) 93 | } 94 | } 95 | 96 | func registerNotification() { 97 | 98 | NotificationCenter.default.addObserver(self, selector:#selector(self.onOpenSQLiteFile(_:)), name:NSNotification.Name.onOpenDBFile, object: nil) 99 | 100 | NotificationCenter.default.addObserver(self, selector:#selector(self.onCloseSQLiteFile(_:)), name:NSNotification.Name.onCloseDBFile, object: nil) 101 | } 102 | 103 | @objc func onOpenSQLiteFile(_ notification: Notification){ 104 | let path = notification.object as! String 105 | self.openDatabaseWithPath(path: path) 106 | } 107 | 108 | @objc func onCloseSQLiteFile(_ notification: Notification){ 109 | self.closeDatabase() 110 | } 111 | 112 | func treeViewMenuConfig() { 113 | self.treeView.tableNodeMenu = self.tableNodeMenu 114 | self.treeView.dataBaseNodeMenu = self.dataBaseNodeMenu 115 | } 116 | 117 | func fetchData() { 118 | self.queue.async { 119 | let dbInfo = DataStoreBO.shared.databaseInfo 120 | 121 | let node = TreeNodeModel() 122 | node.name = dbInfo.dbName 123 | node.type = kDatabaseNode 124 | 125 | let tables = dbInfo.tables 126 | var tableNodes = [TreeNodeModel]() 127 | for table: TableInfo in tables { 128 | let childNode = TreeNodeModel() 129 | childNode.name = table.name 130 | childNode.type = kTableNode 131 | tableNodes.append(childNode) 132 | } 133 | node.childNodes = tableNodes 134 | self.treeViewDelegate.setData(data: node) 135 | DispatchQueue.main.async { 136 | self.treeView.reloadData() 137 | self.treeView.expandItem(nil, expandChildren: true) 138 | self.selectFirstTableNode() 139 | } 140 | } 141 | } 142 | 143 | func selectFirstTableNode() { 144 | let firstTableNode = self.treeView.item(atRow: 1) 145 | if firstTableNode == nil { 146 | return 147 | } 148 | let indexSet = IndexSet.init(integer: 1) 149 | self.treeView.selectRowIndexes(indexSet, byExtendingSelection: false) 150 | //使treeView 成为第一响应者 151 | self.view.window?.makeFirstResponder(self.treeView) 152 | let tableName = (firstTableNode as! TreeNodeModel).name 153 | TableListStateManager.shared.multiDelegate.selectedTableChanged(tableName: tableName) 154 | } 155 | 156 | func openDatabaseWithPath(path: String) { 157 | if !DataStoreBO.shared.openDBWithPath(path: path) { 158 | print("Open Db \(path) Failed!") 159 | return 160 | } 161 | 162 | UserDefaults.standard.set(path, forKey: kSQLiteDBSavePath) 163 | UserDefaults.standard.synchronize() 164 | 165 | self.fetchData() 166 | } 167 | 168 | func closeDatabase() { 169 | DataStoreBO.shared.clear() 170 | self.treeViewDelegate.clearAll() 171 | self.treeView.reloadData() 172 | TableListStateManager.shared.multiDelegate.closeDatabase() 173 | } 174 | 175 | //MARK : Menu Action 176 | 177 | @IBAction func duplicateTableMenuItemClicked(_ sender: AnyObject) { 178 | 179 | } 180 | 181 | @IBAction func renameTableMenuItemClicked(_ sender: AnyObject) { 182 | 183 | } 184 | 185 | @IBAction func dropTableMenuItemClicked(_ sender: AnyObject) { 186 | let selectedRow = self.treeView.selectedRow 187 | if selectedRow < 0 { 188 | return 189 | } 190 | let node = self.treeView.item(atRow: selectedRow) as! TreeNodeModel 191 | 192 | if (node.type == kDatabaseNode) { 193 | return 194 | } 195 | let tableName = node.name 196 | 197 | let alert = NSAlert() 198 | //增加Ok一个按钮 199 | alert.addButton(withTitle: "Ok") 200 | //增加取消一个按钮 201 | alert.addButton(withTitle: "Cancel") 202 | //提示的标题 203 | alert.messageText = "Confirm" 204 | //提示的详细内容 205 | alert.informativeText = "Drop Table \(tableName)?" 206 | //设置告警风格 207 | alert.alertStyle = .critical 208 | //开始显示告警 209 | alert.beginSheetModal(for: self.view.window!, completionHandler: {(returnCode: NSApplication.ModalResponse) -> Void in 210 | print("returnCode \(returnCode)") 211 | if returnCode == NSApplication.ModalResponse.alertFirstButtonReturn { 212 | self.dropTable(tableName) 213 | } 214 | //用户点击告警上面的按钮后的回调 215 | }) 216 | } 217 | 218 | @IBAction func emptyTableMenuItemClicked(_ sender: AnyObject) { 219 | 220 | } 221 | 222 | @IBAction func dataBaseOpenInFinderMenuItemClicked(_ sender: AnyObject) { 223 | 224 | } 225 | 226 | @IBAction func closeDatabaseMenuItemClicked(_ sender: AnyObject) { 227 | 228 | } 229 | 230 | func dropTable(_ tableName: String ) { 231 | 232 | let dropSQL = "Drop Table \(tableName)" 233 | let dao = DataStoreBO.shared.defaultDao 234 | if dao.sqlUpdate(sql: dropSQL) { 235 | DataStoreBO.shared.refreshTables() 236 | self.fetchData() 237 | } 238 | else { 239 | print("Drop Table Failed!") 240 | } 241 | } 242 | 243 | } 244 | 245 | extension TableListViewController: TableListSelectionStateDelegate { 246 | 247 | func registerMultiDelegate() { 248 | TableListStateManager.shared.addMultiDelegate(delegate: self, delegateQueue: nil) 249 | } 250 | 251 | func unRegisterMultiDelegate() { 252 | TableListStateManager.shared.removeMultiDelegate(delegate: self) 253 | } 254 | 255 | func selectedTableChanged(sender: AnyObject, tableName: String) { 256 | print("selectedTableChanged \(tableName)") 257 | } 258 | } 259 | 260 | 261 | -------------------------------------------------------------------------------- /SQLiteApp/Controllers/TableList/TableListViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TableListViewDelegate.swift 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/9/6. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | import Cocoa 10 | 11 | class TableListViewDelegate: TreeViewDataDelegate { 12 | 13 | override func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { 14 | return 28 15 | } 16 | 17 | override func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView { 18 | let model = item as! TreeNodeModel 19 | let type = model.type 20 | let result = outlineView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: type), owner: self)! 21 | let cell = result.subviews[1] as! NSTextField 22 | if model.name != "" { 23 | cell.stringValue = model.name 24 | } 25 | return result 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SQLiteApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSApplicationCategoryType 24 | public.app-category.developer-tools 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSHumanReadableCopyright 28 | Copyright © 2016年 http://www.macdev.io All rights reserved. 29 | NSMainStoryboardFile 30 | Main 31 | NSPrincipalClass 32 | NSApplication 33 | 34 | 35 | -------------------------------------------------------------------------------- /SQLiteApp/MacDev.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/MacDev.sqlite -------------------------------------------------------------------------------- /SQLiteApp/SQLiteApp-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SQLiteApp-Bridging-Header.h 3 | // SQLiteApp 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | #ifndef SQLiteApp_Bridging_Header_h 10 | #define SQLiteApp_Bridging_Header_h 11 | 12 | #import "FMDatabaseQueue.h" 13 | #import "FMDatabase.h" 14 | #import "GCDMulticastDelegate.h" 15 | 16 | 17 | #endif /*SQLiteApp_Bridging_Header_h */ 18 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteApp/Vendor/.DS_Store -------------------------------------------------------------------------------- /SQLiteApp/Vendor/GCDMulticastDelegate/GCDMulticastDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @class GCDMulticastDelegateEnumerator; 4 | 5 | /** 6 | * This class provides multicast delegate functionality. That is: 7 | * - it provides a means for managing a list of delegates 8 | * - any method invocations to an instance of this class are automatically forwarded to all delegates 9 | * 10 | * For example: 11 | * 12 | * // Make this method call on every added delegate (there may be several) 13 | * [multicastDelegate cog:self didFindThing:thing]; 14 | * 15 | * This allows multiple delegates to be added to an xmpp stream or any xmpp module, 16 | * which in turn makes development easier as there can be proper separation of logically different code sections. 17 | * 18 | * In addition, this makes module development easier, 19 | * as multiple delegates can usually be handled in a manner similar to the traditional single delegate paradigm. 20 | * 21 | * This class also provides proper support for GCD queues. 22 | * So each delegate specifies which queue they would like their delegate invocations to be dispatched onto. 23 | * 24 | * All delegate dispatching is done asynchronously (which is a critically important architectural design). 25 | **/ 26 | 27 | @interface GCDMulticastDelegate : NSObject 28 | 29 | - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; 30 | - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue; 31 | - (void)removeDelegate:(id)delegate; 32 | 33 | - (void)removeAllDelegates; 34 | 35 | - (NSUInteger)count; 36 | - (NSUInteger)countOfClass:(Class)aClass; 37 | - (NSUInteger)countForSelector:(SEL)aSelector; 38 | 39 | - (BOOL)hasDelegateThatRespondsToSelector:(SEL)aSelector; 40 | 41 | - (GCDMulticastDelegateEnumerator *)delegateEnumerator; 42 | 43 | @end 44 | 45 | 46 | @interface GCDMulticastDelegateEnumerator : NSObject 47 | 48 | - (NSUInteger)count; 49 | - (NSUInteger)countOfClass:(Class)aClass; 50 | - (NSUInteger)countForSelector:(SEL)aSelector; 51 | 52 | - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr; 53 | - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr ofClass:(Class)aClass; 54 | - (BOOL)getNextDelegate:(id *)delPtr delegateQueue:(dispatch_queue_t *)dqPtr forSelector:(SEL)aSelector; 55 | 56 | @end 57 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDB.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | FOUNDATION_EXPORT double FMDBVersionNumber; 4 | FOUNDATION_EXPORT const unsigned char FMDBVersionString[]; 5 | 6 | #import "FMDatabase.h" 7 | #import "FMResultSet.h" 8 | #import "FMDatabaseAdditions.h" 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabasePool.h" 11 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabaseAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "FMDatabase.h" 11 | 12 | 13 | /** Category of additions for `` class. 14 | 15 | ### See also 16 | 17 | - `` 18 | */ 19 | 20 | @interface FMDatabase (FMDatabaseAdditions) 21 | 22 | ///---------------------------------------- 23 | /// @name Return results of SQL to variable 24 | ///---------------------------------------- 25 | 26 | /** Return `int` value for query 27 | 28 | @param query The SQL query to be performed. 29 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 30 | 31 | @return `int` value. 32 | 33 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 34 | */ 35 | 36 | - (int)intForQuery:(NSString*)query, ...; 37 | 38 | /** Return `long` value for query 39 | 40 | @param query The SQL query to be performed. 41 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 42 | 43 | @return `long` value. 44 | 45 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 46 | */ 47 | 48 | - (long)longForQuery:(NSString*)query, ...; 49 | 50 | /** Return `BOOL` value for query 51 | 52 | @param query The SQL query to be performed. 53 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 54 | 55 | @return `BOOL` value. 56 | 57 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 58 | */ 59 | 60 | - (BOOL)boolForQuery:(NSString*)query, ...; 61 | 62 | /** Return `double` value for query 63 | 64 | @param query The SQL query to be performed. 65 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 66 | 67 | @return `double` value. 68 | 69 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 70 | */ 71 | 72 | - (double)doubleForQuery:(NSString*)query, ...; 73 | 74 | /** Return `NSString` value for query 75 | 76 | @param query The SQL query to be performed. 77 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 78 | 79 | @return `NSString` value. 80 | 81 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 82 | */ 83 | 84 | - (NSString*)stringForQuery:(NSString*)query, ...; 85 | 86 | /** Return `NSData` value for query 87 | 88 | @param query The SQL query to be performed. 89 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 90 | 91 | @return `NSData` value. 92 | 93 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 94 | */ 95 | 96 | - (NSData*)dataForQuery:(NSString*)query, ...; 97 | 98 | /** Return `NSDate` value for query 99 | 100 | @param query The SQL query to be performed. 101 | @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 102 | 103 | @return `NSDate` value. 104 | 105 | @note To use this method from Swift, you must include `FMDatabaseAdditionsVariadic.swift` in your project. 106 | */ 107 | 108 | - (NSDate*)dateForQuery:(NSString*)query, ...; 109 | 110 | 111 | // Notice that there's no dataNoCopyForQuery:. 112 | // That would be a bad idea, because we close out the result set, and then what 113 | // happens to the data that we just didn't copy? Who knows, not I. 114 | 115 | 116 | ///-------------------------------- 117 | /// @name Schema related operations 118 | ///-------------------------------- 119 | 120 | /** Does table exist in database? 121 | 122 | @param tableName The name of the table being looked for. 123 | 124 | @return `YES` if table found; `NO` if not found. 125 | */ 126 | 127 | - (BOOL)tableExists:(NSString*)tableName; 128 | 129 | /** The schema of the database. 130 | 131 | This will be the schema for the entire database. For each entity, each row of the result set will include the following fields: 132 | 133 | - `type` - The type of entity (e.g. table, index, view, or trigger) 134 | - `name` - The name of the object 135 | - `tbl_name` - The name of the table to which the object references 136 | - `rootpage` - The page number of the root b-tree page for tables and indices 137 | - `sql` - The SQL that created the entity 138 | 139 | @return `FMResultSet` of schema; `nil` on error. 140 | 141 | @see [SQLite File Format](http://www.sqlite.org/fileformat.html) 142 | */ 143 | 144 | - (FMResultSet*)getSchema; 145 | 146 | /** The schema of the database. 147 | 148 | This will be the schema for a particular table as report by SQLite `PRAGMA`, for example: 149 | 150 | PRAGMA table_info('employees') 151 | 152 | This will report: 153 | 154 | - `cid` - The column ID number 155 | - `name` - The name of the column 156 | - `type` - The data type specified for the column 157 | - `notnull` - whether the field is defined as NOT NULL (i.e. values required) 158 | - `dflt_value` - The default value for the column 159 | - `pk` - Whether the field is part of the primary key of the table 160 | 161 | @param tableName The name of the table for whom the schema will be returned. 162 | 163 | @return `FMResultSet` of schema; `nil` on error. 164 | 165 | @see [table_info](http://www.sqlite.org/pragma.html#pragma_table_info) 166 | */ 167 | 168 | - (FMResultSet*)getTableSchema:(NSString*)tableName; 169 | 170 | /** Test to see if particular column exists for particular table in database 171 | 172 | @param columnName The name of the column. 173 | 174 | @param tableName The name of the table. 175 | 176 | @return `YES` if column exists in table in question; `NO` otherwise. 177 | */ 178 | 179 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName; 180 | 181 | /** Test to see if particular column exists for particular table in database 182 | 183 | @param columnName The name of the column. 184 | 185 | @param tableName The name of the table. 186 | 187 | @return `YES` if column exists in table in question; `NO` otherwise. 188 | 189 | @see columnExists:inTableWithName: 190 | 191 | @warning Deprecated - use `` instead. 192 | */ 193 | 194 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)); 195 | 196 | 197 | /** Validate SQL statement 198 | 199 | This validates SQL statement by performing `sqlite3_prepare_v2`, but not returning the results, but instead immediately calling `sqlite3_finalize`. 200 | 201 | @param sql The SQL statement being validated. 202 | 203 | @param error This is a pointer to a `NSError` object that will receive the autoreleased `NSError` object if there was any error. If this is `nil`, no `NSError` result will be returned. 204 | 205 | @return `YES` if validation succeeded without incident; `NO` otherwise. 206 | 207 | */ 208 | 209 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error; 210 | 211 | 212 | ///----------------------------------- 213 | /// @name Application identifier tasks 214 | ///----------------------------------- 215 | 216 | /** Retrieve application ID 217 | 218 | @return The `uint32_t` numeric value of the application ID. 219 | 220 | @see setApplicationID: 221 | */ 222 | 223 | - (uint32_t)applicationID; 224 | 225 | /** Set the application ID 226 | 227 | @param appID The `uint32_t` numeric value of the application ID. 228 | 229 | @see applicationID 230 | */ 231 | 232 | - (void)setApplicationID:(uint32_t)appID; 233 | 234 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 235 | /** Retrieve application ID string 236 | 237 | @return The `NSString` value of the application ID. 238 | 239 | @see setApplicationIDString: 240 | */ 241 | 242 | 243 | - (NSString*)applicationIDString; 244 | 245 | /** Set the application ID string 246 | 247 | @param string The `NSString` value of the application ID. 248 | 249 | @see applicationIDString 250 | */ 251 | 252 | - (void)setApplicationIDString:(NSString*)string; 253 | 254 | #endif 255 | 256 | ///----------------------------------- 257 | /// @name user version identifier tasks 258 | ///----------------------------------- 259 | 260 | /** Retrieve user version 261 | 262 | @return The `uint32_t` numeric value of the user version. 263 | 264 | @see setUserVersion: 265 | */ 266 | 267 | - (uint32_t)userVersion; 268 | 269 | /** Set the user-version 270 | 271 | @param version The `uint32_t` numeric value of the user version. 272 | 273 | @see userVersion 274 | */ 275 | 276 | - (void)setUserVersion:(uint32_t)version; 277 | 278 | @end 279 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabaseAdditions.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseAdditions.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 10/30/05. 6 | // Copyright 2005 Flying Meat Inc.. All rights reserved. 7 | // 8 | 9 | #import "FMDatabase.h" 10 | #import "FMDatabaseAdditions.h" 11 | #import "TargetConditionals.h" 12 | 13 | #if FMDB_SQLITE_STANDALONE 14 | #import 15 | #else 16 | #import 17 | #endif 18 | 19 | @interface FMDatabase (PrivateStuff) 20 | - (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args; 21 | @end 22 | 23 | @implementation FMDatabase (FMDatabaseAdditions) 24 | 25 | #define RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(type, sel) \ 26 | va_list args; \ 27 | va_start(args, query); \ 28 | FMResultSet *resultSet = [self executeQuery:query withArgumentsInArray:0x00 orDictionary:0x00 orVAList:args]; \ 29 | va_end(args); \ 30 | if (![resultSet next]) { return (type)0; } \ 31 | type ret = [resultSet sel:0]; \ 32 | [resultSet close]; \ 33 | [resultSet setParentDB:nil]; \ 34 | return ret; 35 | 36 | 37 | - (NSString*)stringForQuery:(NSString*)query, ... { 38 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSString *, stringForColumnIndex); 39 | } 40 | 41 | - (int)intForQuery:(NSString*)query, ... { 42 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(int, intForColumnIndex); 43 | } 44 | 45 | - (long)longForQuery:(NSString*)query, ... { 46 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(long, longForColumnIndex); 47 | } 48 | 49 | - (BOOL)boolForQuery:(NSString*)query, ... { 50 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(BOOL, boolForColumnIndex); 51 | } 52 | 53 | - (double)doubleForQuery:(NSString*)query, ... { 54 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(double, doubleForColumnIndex); 55 | } 56 | 57 | - (NSData*)dataForQuery:(NSString*)query, ... { 58 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSData *, dataForColumnIndex); 59 | } 60 | 61 | - (NSDate*)dateForQuery:(NSString*)query, ... { 62 | RETURN_RESULT_FOR_QUERY_WITH_SELECTOR(NSDate *, dateForColumnIndex); 63 | } 64 | 65 | 66 | - (BOOL)tableExists:(NSString*)tableName { 67 | 68 | tableName = [tableName lowercaseString]; 69 | 70 | FMResultSet *rs = [self executeQuery:@"select [sql] from sqlite_master where [type] = 'table' and lower(name) = ?", tableName]; 71 | 72 | //if at least one next exists, table exists 73 | BOOL returnBool = [rs next]; 74 | 75 | //close and free object 76 | [rs close]; 77 | 78 | return returnBool; 79 | } 80 | 81 | /* 82 | get table with list of tables: result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 83 | check if table exist in database (patch from OZLB) 84 | */ 85 | - (FMResultSet*)getSchema { 86 | 87 | //result colums: type[STRING], name[STRING],tbl_name[STRING],rootpage[INTEGER],sql[STRING] 88 | FMResultSet *rs = [self executeQuery:@"SELECT type, name, tbl_name, rootpage, sql FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type != 'meta' AND name NOT LIKE 'sqlite_%' ORDER BY tbl_name, type DESC, name"]; 89 | 90 | return rs; 91 | } 92 | 93 | /* 94 | get table schema: result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 95 | */ 96 | - (FMResultSet*)getTableSchema:(NSString*)tableName { 97 | 98 | //result colums: cid[INTEGER], name,type [STRING], notnull[INTEGER], dflt_value[],pk[INTEGER] 99 | FMResultSet *rs = [self executeQuery:[NSString stringWithFormat: @"pragma table_info('%@')", tableName]]; 100 | 101 | return rs; 102 | } 103 | 104 | - (BOOL)columnExists:(NSString*)columnName inTableWithName:(NSString*)tableName { 105 | 106 | BOOL returnBool = NO; 107 | 108 | tableName = [tableName lowercaseString]; 109 | columnName = [columnName lowercaseString]; 110 | 111 | FMResultSet *rs = [self getTableSchema:tableName]; 112 | 113 | //check if column is present in table schema 114 | while ([rs next]) { 115 | if ([[[rs stringForColumn:@"name"] lowercaseString] isEqualToString:columnName]) { 116 | returnBool = YES; 117 | break; 118 | } 119 | } 120 | 121 | //If this is not done FMDatabase instance stays out of pool 122 | [rs close]; 123 | 124 | return returnBool; 125 | } 126 | 127 | 128 | 129 | - (uint32_t)applicationID { 130 | #if SQLITE_VERSION_NUMBER >= 3007017 131 | uint32_t r = 0; 132 | 133 | FMResultSet *rs = [self executeQuery:@"pragma application_id"]; 134 | 135 | if ([rs next]) { 136 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 137 | } 138 | 139 | [rs close]; 140 | 141 | return r; 142 | #else 143 | NSString *errorMessage = NSLocalizedString(@"Application ID functions require SQLite 3.7.17", nil); 144 | if (self.logsErrors) NSLog(@"%@", errorMessage); 145 | return 0; 146 | #endif 147 | } 148 | 149 | - (void)setApplicationID:(uint32_t)appID { 150 | #if SQLITE_VERSION_NUMBER >= 3007017 151 | NSString *query = [NSString stringWithFormat:@"pragma application_id=%d", appID]; 152 | FMResultSet *rs = [self executeQuery:query]; 153 | [rs next]; 154 | [rs close]; 155 | #else 156 | NSString *errorMessage = NSLocalizedString(@"Application ID functions require SQLite 3.7.17", nil); 157 | if (self.logsErrors) NSLog(@"%@", errorMessage); 158 | #endif 159 | } 160 | 161 | 162 | #if TARGET_OS_MAC && !TARGET_OS_IPHONE 163 | 164 | - (NSString*)applicationIDString { 165 | #if SQLITE_VERSION_NUMBER >= 3007017 166 | NSString *s = NSFileTypeForHFSTypeCode([self applicationID]); 167 | 168 | assert([s length] == 6); 169 | 170 | s = [s substringWithRange:NSMakeRange(1, 4)]; 171 | 172 | 173 | return s; 174 | #else 175 | NSString *errorMessage = NSLocalizedString(@"Application ID functions require SQLite 3.7.17", nil); 176 | if (self.logsErrors) NSLog(@"%@", errorMessage); 177 | return nil; 178 | #endif 179 | } 180 | 181 | - (void)setApplicationIDString:(NSString*)s { 182 | #if SQLITE_VERSION_NUMBER >= 3007017 183 | if ([s length] != 4) { 184 | NSLog(@"setApplicationIDString: string passed is not exactly 4 chars long. (was %ld)", [s length]); 185 | } 186 | 187 | [self setApplicationID:NSHFSTypeCodeFromFileType([NSString stringWithFormat:@"'%@'", s])]; 188 | #else 189 | NSString *errorMessage = NSLocalizedString(@"Application ID functions require SQLite 3.7.17", nil); 190 | if (self.logsErrors) NSLog(@"%@", errorMessage); 191 | #endif 192 | } 193 | 194 | #endif 195 | 196 | - (uint32_t)userVersion { 197 | uint32_t r = 0; 198 | 199 | FMResultSet *rs = [self executeQuery:@"pragma user_version"]; 200 | 201 | if ([rs next]) { 202 | r = (uint32_t)[rs longLongIntForColumnIndex:0]; 203 | } 204 | 205 | [rs close]; 206 | return r; 207 | } 208 | 209 | - (void)setUserVersion:(uint32_t)version { 210 | NSString *query = [NSString stringWithFormat:@"pragma user_version = %d", version]; 211 | FMResultSet *rs = [self executeQuery:query]; 212 | [rs next]; 213 | [rs close]; 214 | } 215 | 216 | #pragma clang diagnostic push 217 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 218 | 219 | - (BOOL)columnExists:(NSString*)tableName columnName:(NSString*)columnName __attribute__ ((deprecated)) { 220 | return [self columnExists:columnName inTableWithName:tableName]; 221 | } 222 | 223 | #pragma clang diagnostic pop 224 | 225 | 226 | - (BOOL)validateSQL:(NSString*)sql error:(NSError**)error { 227 | sqlite3_stmt *pStmt = NULL; 228 | BOOL validationSucceeded = YES; 229 | 230 | int rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); 231 | if (rc != SQLITE_OK) { 232 | validationSucceeded = NO; 233 | if (error) { 234 | *error = [NSError errorWithDomain:NSCocoaErrorDomain 235 | code:[self lastErrorCode] 236 | userInfo:[NSDictionary dictionaryWithObject:[self lastErrorMessage] 237 | forKey:NSLocalizedDescriptionKey]]; 238 | } 239 | } 240 | 241 | sqlite3_finalize(pStmt); 242 | 243 | return validationSucceeded; 244 | } 245 | 246 | @end 247 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabasePool.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FMDatabase; 12 | 13 | /** Pool of `` objects. 14 | 15 | ### See also 16 | 17 | - `` 18 | - `` 19 | 20 | @warning Before using `FMDatabasePool`, please consider using `` instead. 21 | 22 | If you really really really know what you're doing and `FMDatabasePool` is what 23 | you really really need (ie, you're using a read only database), OK you can use 24 | it. But just be careful not to deadlock! 25 | 26 | For an example on deadlocking, search for: 27 | `ONLY_USE_THE_POOL_IF_YOU_ARE_DOING_READS_OTHERWISE_YOULL_DEADLOCK_USE_FMDATABASEQUEUE_INSTEAD` 28 | in the main.m file. 29 | */ 30 | 31 | @interface FMDatabasePool : NSObject { 32 | NSString *_path; 33 | 34 | dispatch_queue_t _lockQueue; 35 | 36 | NSMutableArray *_databaseInPool; 37 | NSMutableArray *_databaseOutPool; 38 | 39 | __unsafe_unretained id _delegate; 40 | 41 | NSUInteger _maximumNumberOfDatabasesToCreate; 42 | int _openFlags; 43 | NSString *_vfsName; 44 | } 45 | 46 | /** Database path */ 47 | 48 | @property (atomic, retain) NSString *path; 49 | 50 | /** Delegate object */ 51 | 52 | @property (atomic, assign) id delegate; 53 | 54 | /** Maximum number of databases to create */ 55 | 56 | @property (atomic, assign) NSUInteger maximumNumberOfDatabasesToCreate; 57 | 58 | /** Open flags */ 59 | 60 | @property (atomic, readonly) int openFlags; 61 | 62 | /** Custom virtual file system name */ 63 | 64 | @property (atomic, copy) NSString *vfsName; 65 | 66 | 67 | ///--------------------- 68 | /// @name Initialization 69 | ///--------------------- 70 | 71 | /** Create pool using path. 72 | 73 | @param aPath The file path of the database. 74 | 75 | @return The `FMDatabasePool` object. `nil` on error. 76 | */ 77 | 78 | + (instancetype)databasePoolWithPath:(NSString*)aPath; 79 | 80 | /** Create pool using path and specified flags 81 | 82 | @param aPath The file path of the database. 83 | @param openFlags Flags passed to the openWithFlags method of the database 84 | 85 | @return The `FMDatabasePool` object. `nil` on error. 86 | */ 87 | 88 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags; 89 | 90 | /** Create pool using path. 91 | 92 | @param aPath The file path of the database. 93 | 94 | @return The `FMDatabasePool` object. `nil` on error. 95 | */ 96 | 97 | - (instancetype)initWithPath:(NSString*)aPath; 98 | 99 | /** Create pool using path and specified flags. 100 | 101 | @param aPath The file path of the database. 102 | @param openFlags Flags passed to the openWithFlags method of the database 103 | 104 | @return The `FMDatabasePool` object. `nil` on error. 105 | */ 106 | 107 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 108 | 109 | /** Create pool using path and specified flags. 110 | 111 | @param aPath The file path of the database. 112 | @param openFlags Flags passed to the openWithFlags method of the database 113 | @param vfsName The name of a custom virtual file system 114 | 115 | @return The `FMDatabasePool` object. `nil` on error. 116 | */ 117 | 118 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName; 119 | 120 | /** Returns the Class of 'FMDatabase' subclass, that will be used to instantiate database object. 121 | 122 | Subclasses can override this method to return specified Class of 'FMDatabase' subclass. 123 | 124 | @return The Class of 'FMDatabase' subclass, that will be used to instantiate database object. 125 | */ 126 | 127 | + (Class)databaseClass; 128 | 129 | ///------------------------------------------------ 130 | /// @name Keeping track of checked in/out databases 131 | ///------------------------------------------------ 132 | 133 | /** Number of checked-in databases in pool 134 | 135 | @returns Number of databases 136 | */ 137 | 138 | - (NSUInteger)countOfCheckedInDatabases; 139 | 140 | /** Number of checked-out databases in pool 141 | 142 | @returns Number of databases 143 | */ 144 | 145 | - (NSUInteger)countOfCheckedOutDatabases; 146 | 147 | /** Total number of databases in pool 148 | 149 | @returns Number of databases 150 | */ 151 | 152 | - (NSUInteger)countOfOpenDatabases; 153 | 154 | /** Release all databases in pool */ 155 | 156 | - (void)releaseAllDatabases; 157 | 158 | ///------------------------------------------ 159 | /// @name Perform database operations in pool 160 | ///------------------------------------------ 161 | 162 | /** Synchronously perform database operations in pool. 163 | 164 | @param block The code to be run on the `FMDatabasePool` pool. 165 | */ 166 | 167 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 168 | 169 | /** Synchronously perform database operations in pool using transaction. 170 | 171 | @param block The code to be run on the `FMDatabasePool` pool. 172 | */ 173 | 174 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 175 | 176 | /** Synchronously perform database operations in pool using deferred transaction. 177 | 178 | @param block The code to be run on the `FMDatabasePool` pool. 179 | */ 180 | 181 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 182 | 183 | /** Synchronously perform database operations in pool using save point. 184 | 185 | @param block The code to be run on the `FMDatabasePool` pool. 186 | 187 | @return `NSError` object if error; `nil` if successful. 188 | 189 | @warning You can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. If you need to nest, use `<[FMDatabase startSavePointWithName:error:]>` instead. 190 | */ 191 | 192 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 193 | 194 | @end 195 | 196 | 197 | /** FMDatabasePool delegate category 198 | 199 | This is a category that defines the protocol for the FMDatabasePool delegate 200 | */ 201 | 202 | @interface NSObject (FMDatabasePoolDelegate) 203 | 204 | /** Asks the delegate whether database should be added to the pool. 205 | 206 | @param pool The `FMDatabasePool` object. 207 | @param database The `FMDatabase` object. 208 | 209 | @return `YES` if it should add database to pool; `NO` if not. 210 | 211 | */ 212 | 213 | - (BOOL)databasePool:(FMDatabasePool*)pool shouldAddDatabaseToPool:(FMDatabase*)database; 214 | 215 | /** Tells the delegate that database was added to the pool. 216 | 217 | @param pool The `FMDatabasePool` object. 218 | @param database The `FMDatabase` object. 219 | 220 | */ 221 | 222 | - (void)databasePool:(FMDatabasePool*)pool didAddDatabase:(FMDatabase*)database; 223 | 224 | @end 225 | 226 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabasePool.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabasePool.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #if FMDB_SQLITE_STANDALONE 10 | #import 11 | #else 12 | #import 13 | #endif 14 | 15 | #import "FMDatabasePool.h" 16 | #import "FMDatabase.h" 17 | 18 | @interface FMDatabasePool() 19 | 20 | - (void)pushDatabaseBackInPool:(FMDatabase*)db; 21 | - (FMDatabase*)db; 22 | 23 | @end 24 | 25 | 26 | @implementation FMDatabasePool 27 | @synthesize path=_path; 28 | @synthesize delegate=_delegate; 29 | @synthesize maximumNumberOfDatabasesToCreate=_maximumNumberOfDatabasesToCreate; 30 | @synthesize openFlags=_openFlags; 31 | 32 | 33 | + (instancetype)databasePoolWithPath:(NSString*)aPath { 34 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath]); 35 | } 36 | 37 | + (instancetype)databasePoolWithPath:(NSString*)aPath flags:(int)openFlags { 38 | return FMDBReturnAutoreleased([[self alloc] initWithPath:aPath flags:openFlags]); 39 | } 40 | 41 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName { 42 | 43 | self = [super init]; 44 | 45 | if (self != nil) { 46 | _path = [aPath copy]; 47 | _lockQueue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 48 | _databaseInPool = FMDBReturnRetained([NSMutableArray array]); 49 | _databaseOutPool = FMDBReturnRetained([NSMutableArray array]); 50 | _openFlags = openFlags; 51 | _vfsName = [vfsName copy]; 52 | } 53 | 54 | return self; 55 | } 56 | 57 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 58 | return [self initWithPath:aPath flags:openFlags vfs:nil]; 59 | } 60 | 61 | - (instancetype)initWithPath:(NSString*)aPath 62 | { 63 | // default flags for sqlite3_open 64 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE]; 65 | } 66 | 67 | - (instancetype)init { 68 | return [self initWithPath:nil]; 69 | } 70 | 71 | + (Class)databaseClass { 72 | return [FMDatabase class]; 73 | } 74 | 75 | - (void)dealloc { 76 | 77 | _delegate = 0x00; 78 | FMDBRelease(_path); 79 | FMDBRelease(_databaseInPool); 80 | FMDBRelease(_databaseOutPool); 81 | 82 | if (_lockQueue) { 83 | FMDBDispatchQueueRelease(_lockQueue); 84 | _lockQueue = 0x00; 85 | } 86 | #if ! __has_feature(objc_arc) 87 | [super dealloc]; 88 | #endif 89 | } 90 | 91 | 92 | - (void)executeLocked:(void (^)(void))aBlock { 93 | dispatch_sync(_lockQueue, aBlock); 94 | } 95 | 96 | - (void)pushDatabaseBackInPool:(FMDatabase*)db { 97 | 98 | if (!db) { // db can be null if we set an upper bound on the # of databases to create. 99 | return; 100 | } 101 | 102 | [self executeLocked:^() { 103 | 104 | if ([self->_databaseInPool containsObject:db]) { 105 | [[NSException exceptionWithName:@"Database already in pool" reason:@"The FMDatabase being put back into the pool is already present in the pool" userInfo:nil] raise]; 106 | } 107 | 108 | [self->_databaseInPool addObject:db]; 109 | [self->_databaseOutPool removeObject:db]; 110 | 111 | }]; 112 | } 113 | 114 | - (FMDatabase*)db { 115 | 116 | __block FMDatabase *db; 117 | 118 | 119 | [self executeLocked:^() { 120 | db = [self->_databaseInPool lastObject]; 121 | 122 | BOOL shouldNotifyDelegate = NO; 123 | 124 | if (db) { 125 | [self->_databaseOutPool addObject:db]; 126 | [self->_databaseInPool removeLastObject]; 127 | } 128 | else { 129 | 130 | if (self->_maximumNumberOfDatabasesToCreate) { 131 | NSUInteger currentCount = [self->_databaseOutPool count] + [self->_databaseInPool count]; 132 | 133 | if (currentCount >= self->_maximumNumberOfDatabasesToCreate) { 134 | NSLog(@"Maximum number of databases (%ld) has already been reached!", (long)currentCount); 135 | return; 136 | } 137 | } 138 | 139 | db = [[[self class] databaseClass] databaseWithPath:self->_path]; 140 | shouldNotifyDelegate = YES; 141 | } 142 | 143 | //This ensures that the db is opened before returning 144 | #if SQLITE_VERSION_NUMBER >= 3005000 145 | BOOL success = [db openWithFlags:self->_openFlags vfs:self->_vfsName]; 146 | #else 147 | BOOL success = [db open]; 148 | #endif 149 | if (success) { 150 | if ([self->_delegate respondsToSelector:@selector(databasePool:shouldAddDatabaseToPool:)] && ![self->_delegate databasePool:self shouldAddDatabaseToPool:db]) { 151 | [db close]; 152 | db = 0x00; 153 | } 154 | else { 155 | //It should not get added in the pool twice if lastObject was found 156 | if (![self->_databaseOutPool containsObject:db]) { 157 | [self->_databaseOutPool addObject:db]; 158 | 159 | if (shouldNotifyDelegate && [self->_delegate respondsToSelector:@selector(databasePool:didAddDatabase:)]) { 160 | [self->_delegate databasePool:self didAddDatabase:db]; 161 | } 162 | } 163 | } 164 | } 165 | else { 166 | NSLog(@"Could not open up the database at path %@", self->_path); 167 | db = 0x00; 168 | } 169 | }]; 170 | 171 | return db; 172 | } 173 | 174 | - (NSUInteger)countOfCheckedInDatabases { 175 | 176 | __block NSUInteger count; 177 | 178 | [self executeLocked:^() { 179 | count = [self->_databaseInPool count]; 180 | }]; 181 | 182 | return count; 183 | } 184 | 185 | - (NSUInteger)countOfCheckedOutDatabases { 186 | 187 | __block NSUInteger count; 188 | 189 | [self executeLocked:^() { 190 | count = [self->_databaseOutPool count]; 191 | }]; 192 | 193 | return count; 194 | } 195 | 196 | - (NSUInteger)countOfOpenDatabases { 197 | __block NSUInteger count; 198 | 199 | [self executeLocked:^() { 200 | count = [self->_databaseOutPool count] + [self->_databaseInPool count]; 201 | }]; 202 | 203 | return count; 204 | } 205 | 206 | - (void)releaseAllDatabases { 207 | [self executeLocked:^() { 208 | [self->_databaseOutPool removeAllObjects]; 209 | [self->_databaseInPool removeAllObjects]; 210 | }]; 211 | } 212 | 213 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 214 | 215 | FMDatabase *db = [self db]; 216 | 217 | block(db); 218 | 219 | [self pushDatabaseBackInPool:db]; 220 | } 221 | 222 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 223 | 224 | BOOL shouldRollback = NO; 225 | 226 | FMDatabase *db = [self db]; 227 | 228 | if (useDeferred) { 229 | [db beginDeferredTransaction]; 230 | } 231 | else { 232 | [db beginTransaction]; 233 | } 234 | 235 | 236 | block(db, &shouldRollback); 237 | 238 | if (shouldRollback) { 239 | [db rollback]; 240 | } 241 | else { 242 | [db commit]; 243 | } 244 | 245 | [self pushDatabaseBackInPool:db]; 246 | } 247 | 248 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 249 | [self beginTransaction:YES withBlock:block]; 250 | } 251 | 252 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 253 | [self beginTransaction:NO withBlock:block]; 254 | } 255 | 256 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 257 | #if SQLITE_VERSION_NUMBER >= 3007000 258 | static unsigned long savePointIdx = 0; 259 | 260 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 261 | 262 | BOOL shouldRollback = NO; 263 | 264 | FMDatabase *db = [self db]; 265 | 266 | NSError *err = 0x00; 267 | 268 | if (![db startSavePointWithName:name error:&err]) { 269 | [self pushDatabaseBackInPool:db]; 270 | return err; 271 | } 272 | 273 | block(db, &shouldRollback); 274 | 275 | if (shouldRollback) { 276 | // We need to rollback and release this savepoint to remove it 277 | [db rollbackToSavePointWithName:name error:&err]; 278 | } 279 | [db releaseSavePointWithName:name error:&err]; 280 | 281 | [self pushDatabaseBackInPool:db]; 282 | 283 | return err; 284 | #else 285 | NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil); 286 | if (self.logsErrors) NSLog(@"%@", errorMessage); 287 | return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; 288 | #endif 289 | } 290 | 291 | @end 292 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabaseQueue.h: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.h 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FMDatabase; 12 | 13 | /** To perform queries and updates on multiple threads, you'll want to use `FMDatabaseQueue`. 14 | 15 | Using a single instance of `` from multiple threads at once is a bad idea. It has always been OK to make a `` object *per thread*. Just don't share a single instance across threads, and definitely not across multiple threads at the same time. 16 | 17 | Instead, use `FMDatabaseQueue`. Here's how to use it: 18 | 19 | First, make your queue. 20 | 21 | FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; 22 | 23 | Then use it like so: 24 | 25 | [queue inDatabase:^(FMDatabase *db) { 26 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 27 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 28 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 29 | 30 | FMResultSet *rs = [db executeQuery:@"select * from foo"]; 31 | while ([rs next]) { 32 | //… 33 | } 34 | }]; 35 | 36 | An easy way to wrap things up in a transaction can be done like this: 37 | 38 | [queue inTransaction:^(FMDatabase *db, BOOL *rollback) { 39 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]]; 40 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]]; 41 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]]; 42 | 43 | if (whoopsSomethingWrongHappened) { 44 | *rollback = YES; 45 | return; 46 | } 47 | // etc… 48 | [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]]; 49 | }]; 50 | 51 | `FMDatabaseQueue` will run the blocks on a serialized queue (hence the name of the class). So if you call `FMDatabaseQueue`'s methods from multiple threads at the same time, they will be executed in the order they are received. This way queries and updates won't step on each other's toes, and every one is happy. 52 | 53 | ### See also 54 | 55 | - `` 56 | 57 | @warning Do not instantiate a single `` object and use it across multiple threads. Use `FMDatabaseQueue` instead. 58 | 59 | @warning The calls to `FMDatabaseQueue`'s methods are blocking. So even though you are passing along blocks, they will **not** be run on another thread. 60 | 61 | */ 62 | 63 | @interface FMDatabaseQueue : NSObject { 64 | NSString *_path; 65 | dispatch_queue_t _queue; 66 | FMDatabase *_db; 67 | int _openFlags; 68 | NSString *_vfsName; 69 | } 70 | 71 | /** Path of database */ 72 | 73 | @property (atomic, retain) NSString *path; 74 | 75 | /** Open flags */ 76 | 77 | @property (atomic, readonly) int openFlags; 78 | 79 | /** Custom virtual file system name */ 80 | 81 | @property (atomic, copy) NSString *vfsName; 82 | 83 | ///---------------------------------------------------- 84 | /// @name Initialization, opening, and closing of queue 85 | ///---------------------------------------------------- 86 | 87 | /** Create queue using path. 88 | 89 | @param aPath The file path of the database. 90 | 91 | @return The `FMDatabaseQueue` object. `nil` on error. 92 | */ 93 | 94 | + (instancetype)databaseQueueWithPath:(NSString*)aPath; 95 | 96 | /** Create queue using path and specified flags. 97 | 98 | @param aPath The file path of the database. 99 | @param openFlags Flags passed to the openWithFlags method of the database 100 | 101 | @return The `FMDatabaseQueue` object. `nil` on error. 102 | */ 103 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags; 104 | 105 | /** Create queue using path. 106 | 107 | @param aPath The file path of the database. 108 | 109 | @return The `FMDatabaseQueue` object. `nil` on error. 110 | */ 111 | 112 | - (instancetype)initWithPath:(NSString*)aPath; 113 | 114 | /** Create queue using path and specified flags. 115 | 116 | @param aPath The file path of the database. 117 | @param openFlags Flags passed to the openWithFlags method of the database 118 | 119 | @return The `FMDatabaseQueue` object. `nil` on error. 120 | */ 121 | 122 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags; 123 | 124 | /** Create queue using path and specified flags. 125 | 126 | @param aPath The file path of the database. 127 | @param openFlags Flags passed to the openWithFlags method of the database 128 | @param vfsName The name of a custom virtual file system 129 | 130 | @return The `FMDatabaseQueue` object. `nil` on error. 131 | */ 132 | 133 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName; 134 | 135 | /** Returns the Class of 'FMDatabase' subclass, that will be used to instantiate database object. 136 | 137 | Subclasses can override this method to return specified Class of 'FMDatabase' subclass. 138 | 139 | @return The Class of 'FMDatabase' subclass, that will be used to instantiate database object. 140 | */ 141 | 142 | + (Class)databaseClass; 143 | 144 | /** Close database used by queue. */ 145 | 146 | - (void)close; 147 | 148 | /** Interupt pending database operation. */ 149 | 150 | - (void)interrupt; 151 | 152 | ///----------------------------------------------- 153 | /// @name Dispatching database operations to queue 154 | ///----------------------------------------------- 155 | 156 | /** Synchronously perform database operations on queue. 157 | 158 | @param block The code to be run on the queue of `FMDatabaseQueue` 159 | */ 160 | 161 | - (void)inDatabase:(void (^)(FMDatabase *db))block; 162 | 163 | /** Synchronously perform database operations on queue, using transactions. 164 | 165 | @param block The code to be run on the queue of `FMDatabaseQueue` 166 | */ 167 | 168 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 169 | 170 | /** Synchronously perform database operations on queue, using deferred transactions. 171 | 172 | @param block The code to be run on the queue of `FMDatabaseQueue` 173 | */ 174 | 175 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block; 176 | 177 | ///----------------------------------------------- 178 | /// @name Dispatching database operations to queue 179 | ///----------------------------------------------- 180 | 181 | /** Synchronously perform database operations using save point. 182 | 183 | @param block The code to be run on the queue of `FMDatabaseQueue` 184 | */ 185 | 186 | // NOTE: you can not nest these, since calling it will pull another database out of the pool and you'll get a deadlock. 187 | // If you need to nest, use FMDatabase's startSavePointWithName:error: instead. 188 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block; 189 | 190 | @end 191 | 192 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMDatabaseQueue.m: -------------------------------------------------------------------------------- 1 | // 2 | // FMDatabaseQueue.m 3 | // fmdb 4 | // 5 | // Created by August Mueller on 6/22/11. 6 | // Copyright 2011 Flying Meat Inc. All rights reserved. 7 | // 8 | 9 | #import "FMDatabaseQueue.h" 10 | #import "FMDatabase.h" 11 | 12 | #if FMDB_SQLITE_STANDALONE 13 | #import 14 | #else 15 | #import 16 | #endif 17 | 18 | /* 19 | 20 | Note: we call [self retain]; before using dispatch_sync, just incase 21 | FMDatabaseQueue is released on another thread and we're in the middle of doing 22 | something in dispatch_sync 23 | 24 | */ 25 | 26 | /* 27 | * A key used to associate the FMDatabaseQueue object with the dispatch_queue_t it uses. 28 | * This in turn is used for deadlock detection by seeing if inDatabase: is called on 29 | * the queue's dispatch queue, which should not happen and causes a deadlock. 30 | */ 31 | static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey; 32 | 33 | @implementation FMDatabaseQueue 34 | 35 | @synthesize path = _path; 36 | @synthesize openFlags = _openFlags; 37 | @synthesize vfsName = _vfsName; 38 | 39 | + (instancetype)databaseQueueWithPath:(NSString*)aPath { 40 | 41 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath]; 42 | 43 | FMDBAutorelease(q); 44 | 45 | return q; 46 | } 47 | 48 | + (instancetype)databaseQueueWithPath:(NSString*)aPath flags:(int)openFlags { 49 | 50 | FMDatabaseQueue *q = [[self alloc] initWithPath:aPath flags:openFlags]; 51 | 52 | FMDBAutorelease(q); 53 | 54 | return q; 55 | } 56 | 57 | + (Class)databaseClass { 58 | return [FMDatabase class]; 59 | } 60 | 61 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName { 62 | 63 | self = [super init]; 64 | 65 | if (self != nil) { 66 | 67 | _db = [[[self class] databaseClass] databaseWithPath:aPath]; 68 | FMDBRetain(_db); 69 | 70 | #if SQLITE_VERSION_NUMBER >= 3005000 71 | BOOL success = [_db openWithFlags:openFlags vfs:vfsName]; 72 | #else 73 | BOOL success = [_db open]; 74 | #endif 75 | if (!success) { 76 | NSLog(@"Could not create database queue for path %@", aPath); 77 | FMDBRelease(self); 78 | return 0x00; 79 | } 80 | 81 | _path = FMDBReturnRetained(aPath); 82 | 83 | _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); 84 | dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); 85 | _openFlags = openFlags; 86 | _vfsName = [vfsName copy]; 87 | } 88 | 89 | return self; 90 | } 91 | 92 | - (instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags { 93 | return [self initWithPath:aPath flags:openFlags vfs:nil]; 94 | } 95 | 96 | - (instancetype)initWithPath:(NSString*)aPath { 97 | 98 | // default flags for sqlite3_open 99 | return [self initWithPath:aPath flags:SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE vfs:nil]; 100 | } 101 | 102 | - (instancetype)init { 103 | return [self initWithPath:nil]; 104 | } 105 | 106 | 107 | - (void)dealloc { 108 | 109 | FMDBRelease(_db); 110 | FMDBRelease(_path); 111 | 112 | if (_queue) { 113 | FMDBDispatchQueueRelease(_queue); 114 | _queue = 0x00; 115 | } 116 | #if ! __has_feature(objc_arc) 117 | [super dealloc]; 118 | #endif 119 | } 120 | 121 | - (void)close { 122 | FMDBRetain(self); 123 | dispatch_sync(_queue, ^() { 124 | [self->_db close]; 125 | FMDBRelease(_db); 126 | self->_db = 0x00; 127 | }); 128 | FMDBRelease(self); 129 | } 130 | 131 | - (void)interrupt 132 | { 133 | [[self database] interrupt]; 134 | } 135 | 136 | - (FMDatabase*)database { 137 | if (!_db) { 138 | _db = FMDBReturnRetained([[[self class] databaseClass] databaseWithPath:_path]); 139 | 140 | #if SQLITE_VERSION_NUMBER >= 3005000 141 | BOOL success = [_db openWithFlags:_openFlags vfs:_vfsName]; 142 | #else 143 | BOOL success = [_db open]; 144 | #endif 145 | if (!success) { 146 | NSLog(@"FMDatabaseQueue could not reopen database for path %@", _path); 147 | FMDBRelease(_db); 148 | _db = 0x00; 149 | return 0x00; 150 | } 151 | } 152 | 153 | return _db; 154 | } 155 | 156 | - (void)inDatabase:(void (^)(FMDatabase *db))block { 157 | /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue 158 | * and then check it against self to make sure we're not about to deadlock. */ 159 | FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); 160 | assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); 161 | 162 | FMDBRetain(self); 163 | 164 | dispatch_sync(_queue, ^() { 165 | 166 | FMDatabase *db = [self database]; 167 | block(db); 168 | 169 | if ([db hasOpenResultSets]) { 170 | NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); 171 | 172 | #if defined(DEBUG) && DEBUG 173 | NSSet *openSetCopy = FMDBReturnAutoreleased([[db valueForKey:@"_openResultSets"] copy]); 174 | for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { 175 | FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; 176 | NSLog(@"query: '%@'", [rs query]); 177 | } 178 | #endif 179 | } 180 | }); 181 | 182 | FMDBRelease(self); 183 | } 184 | 185 | 186 | - (void)beginTransaction:(BOOL)useDeferred withBlock:(void (^)(FMDatabase *db, BOOL *rollback))block { 187 | FMDBRetain(self); 188 | dispatch_sync(_queue, ^() { 189 | 190 | BOOL shouldRollback = NO; 191 | 192 | if (useDeferred) { 193 | [[self database] beginDeferredTransaction]; 194 | } 195 | else { 196 | [[self database] beginTransaction]; 197 | } 198 | 199 | block([self database], &shouldRollback); 200 | 201 | if (shouldRollback) { 202 | [[self database] rollback]; 203 | } 204 | else { 205 | [[self database] commit]; 206 | } 207 | }); 208 | 209 | FMDBRelease(self); 210 | } 211 | 212 | - (void)inDeferredTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 213 | [self beginTransaction:YES withBlock:block]; 214 | } 215 | 216 | - (void)inTransaction:(void (^)(FMDatabase *db, BOOL *rollback))block { 217 | [self beginTransaction:NO withBlock:block]; 218 | } 219 | 220 | - (NSError*)inSavePoint:(void (^)(FMDatabase *db, BOOL *rollback))block { 221 | #if SQLITE_VERSION_NUMBER >= 3007000 222 | static unsigned long savePointIdx = 0; 223 | __block NSError *err = 0x00; 224 | FMDBRetain(self); 225 | dispatch_sync(_queue, ^() { 226 | 227 | NSString *name = [NSString stringWithFormat:@"savePoint%ld", savePointIdx++]; 228 | 229 | BOOL shouldRollback = NO; 230 | 231 | if ([[self database] startSavePointWithName:name error:&err]) { 232 | 233 | block([self database], &shouldRollback); 234 | 235 | if (shouldRollback) { 236 | // We need to rollback and release this savepoint to remove it 237 | [[self database] rollbackToSavePointWithName:name error:&err]; 238 | } 239 | [[self database] releaseSavePointWithName:name error:&err]; 240 | 241 | } 242 | }); 243 | FMDBRelease(self); 244 | return err; 245 | #else 246 | NSString *errorMessage = NSLocalizedString(@"Save point functions require SQLite 3.7", nil); 247 | if (self.logsErrors) NSLog(@"%@", errorMessage); 248 | return [NSError errorWithDomain:@"FMDatabase" code:0 userInfo:@{NSLocalizedDescriptionKey : errorMessage}]; 249 | #endif 250 | } 251 | 252 | @end 253 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/FMResultSet.m: -------------------------------------------------------------------------------- 1 | #import "FMResultSet.h" 2 | #import "FMDatabase.h" 3 | #import "unistd.h" 4 | 5 | #if FMDB_SQLITE_STANDALONE 6 | #import 7 | #else 8 | #import 9 | #endif 10 | 11 | @interface FMDatabase () 12 | - (void)resultSetDidClose:(FMResultSet *)resultSet; 13 | @end 14 | 15 | 16 | @implementation FMResultSet 17 | @synthesize query=_query; 18 | @synthesize statement=_statement; 19 | 20 | + (instancetype)resultSetWithStatement:(FMStatement *)statement usingParentDatabase:(FMDatabase*)aDB { 21 | 22 | FMResultSet *rs = [[FMResultSet alloc] init]; 23 | 24 | [rs setStatement:statement]; 25 | [rs setParentDB:aDB]; 26 | 27 | NSParameterAssert(![statement inUse]); 28 | [statement setInUse:YES]; // weak reference 29 | 30 | return FMDBReturnAutoreleased(rs); 31 | } 32 | 33 | - (void)finalize { 34 | [self close]; 35 | [super finalize]; 36 | } 37 | 38 | - (void)dealloc { 39 | [self close]; 40 | 41 | FMDBRelease(_query); 42 | _query = nil; 43 | 44 | FMDBRelease(_columnNameToIndexMap); 45 | _columnNameToIndexMap = nil; 46 | 47 | #if ! __has_feature(objc_arc) 48 | [super dealloc]; 49 | #endif 50 | } 51 | 52 | - (void)close { 53 | [_statement reset]; 54 | FMDBRelease(_statement); 55 | _statement = nil; 56 | 57 | // we don't need this anymore... (i think) 58 | //[_parentDB setInUse:NO]; 59 | [_parentDB resultSetDidClose:self]; 60 | _parentDB = nil; 61 | } 62 | 63 | - (int)columnCount { 64 | return sqlite3_column_count([_statement statement]); 65 | } 66 | 67 | - (NSMutableDictionary *)columnNameToIndexMap { 68 | if (!_columnNameToIndexMap) { 69 | int columnCount = sqlite3_column_count([_statement statement]); 70 | _columnNameToIndexMap = [[NSMutableDictionary alloc] initWithCapacity:(NSUInteger)columnCount]; 71 | int columnIdx = 0; 72 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 73 | [_columnNameToIndexMap setObject:[NSNumber numberWithInt:columnIdx] 74 | forKey:[[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)] lowercaseString]]; 75 | } 76 | } 77 | return _columnNameToIndexMap; 78 | } 79 | 80 | - (void)kvcMagic:(id)object { 81 | 82 | int columnCount = sqlite3_column_count([_statement statement]); 83 | 84 | int columnIdx = 0; 85 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 86 | 87 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 88 | 89 | // check for a null row 90 | if (c) { 91 | NSString *s = [NSString stringWithUTF8String:c]; 92 | 93 | [object setValue:s forKey:[NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]]; 94 | } 95 | } 96 | } 97 | 98 | #pragma clang diagnostic push 99 | #pragma clang diagnostic ignored "-Wdeprecated-implementations" 100 | 101 | - (NSDictionary*)resultDict { 102 | 103 | NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); 104 | 105 | if (num_cols > 0) { 106 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 107 | 108 | NSEnumerator *columnNames = [[self columnNameToIndexMap] keyEnumerator]; 109 | NSString *columnName = nil; 110 | while ((columnName = [columnNames nextObject])) { 111 | id objectValue = [self objectForColumnName:columnName]; 112 | [dict setObject:objectValue forKey:columnName]; 113 | } 114 | 115 | return FMDBReturnAutoreleased([dict copy]); 116 | } 117 | else { 118 | NSLog(@"Warning: There seem to be no columns in this set."); 119 | } 120 | 121 | return nil; 122 | } 123 | 124 | #pragma clang diagnostic pop 125 | 126 | - (NSDictionary*)resultDictionary { 127 | 128 | NSUInteger num_cols = (NSUInteger)sqlite3_data_count([_statement statement]); 129 | 130 | if (num_cols > 0) { 131 | NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithCapacity:num_cols]; 132 | 133 | int columnCount = sqlite3_column_count([_statement statement]); 134 | 135 | int columnIdx = 0; 136 | for (columnIdx = 0; columnIdx < columnCount; columnIdx++) { 137 | 138 | NSString *columnName = [NSString stringWithUTF8String:sqlite3_column_name([_statement statement], columnIdx)]; 139 | id objectValue = [self objectForColumnIndex:columnIdx]; 140 | [dict setObject:objectValue forKey:columnName]; 141 | } 142 | 143 | return dict; 144 | } 145 | else { 146 | NSLog(@"Warning: There seem to be no columns in this set."); 147 | } 148 | 149 | return nil; 150 | } 151 | 152 | 153 | 154 | 155 | - (BOOL)next { 156 | return [self nextWithError:nil]; 157 | } 158 | 159 | - (BOOL)nextWithError:(NSError **)outErr { 160 | 161 | int rc = sqlite3_step([_statement statement]); 162 | 163 | if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { 164 | NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); 165 | NSLog(@"Database busy"); 166 | if (outErr) { 167 | *outErr = [_parentDB lastError]; 168 | } 169 | } 170 | else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { 171 | // all is well, let's return. 172 | } 173 | else if (SQLITE_ERROR == rc) { 174 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 175 | if (outErr) { 176 | *outErr = [_parentDB lastError]; 177 | } 178 | } 179 | else if (SQLITE_MISUSE == rc) { 180 | // uh oh. 181 | NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 182 | if (outErr) { 183 | if (_parentDB) { 184 | *outErr = [_parentDB lastError]; 185 | } 186 | else { 187 | // If 'next' or 'nextWithError' is called after the result set is closed, 188 | // we need to return the appropriate error. 189 | NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:@"parentDB does not exist" forKey:NSLocalizedDescriptionKey]; 190 | *outErr = [NSError errorWithDomain:@"FMDatabase" code:SQLITE_MISUSE userInfo:errorMessage]; 191 | } 192 | 193 | } 194 | } 195 | else { 196 | // wtf? 197 | NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); 198 | if (outErr) { 199 | *outErr = [_parentDB lastError]; 200 | } 201 | } 202 | 203 | 204 | if (rc != SQLITE_ROW) { 205 | [self close]; 206 | } 207 | 208 | return (rc == SQLITE_ROW); 209 | } 210 | 211 | - (BOOL)hasAnotherRow { 212 | return sqlite3_errcode([_parentDB sqliteHandle]) == SQLITE_ROW; 213 | } 214 | 215 | - (int)columnIndexForName:(NSString*)columnName { 216 | columnName = [columnName lowercaseString]; 217 | 218 | NSNumber *n = [[self columnNameToIndexMap] objectForKey:columnName]; 219 | 220 | if (n) { 221 | return [n intValue]; 222 | } 223 | 224 | NSLog(@"Warning: I could not find the column named '%@'.", columnName); 225 | 226 | return -1; 227 | } 228 | 229 | 230 | 231 | - (int)intForColumn:(NSString*)columnName { 232 | return [self intForColumnIndex:[self columnIndexForName:columnName]]; 233 | } 234 | 235 | - (int)intForColumnIndex:(int)columnIdx { 236 | return sqlite3_column_int([_statement statement], columnIdx); 237 | } 238 | 239 | - (long)longForColumn:(NSString*)columnName { 240 | return [self longForColumnIndex:[self columnIndexForName:columnName]]; 241 | } 242 | 243 | - (long)longForColumnIndex:(int)columnIdx { 244 | return (long)sqlite3_column_int64([_statement statement], columnIdx); 245 | } 246 | 247 | - (long long int)longLongIntForColumn:(NSString*)columnName { 248 | return [self longLongIntForColumnIndex:[self columnIndexForName:columnName]]; 249 | } 250 | 251 | - (long long int)longLongIntForColumnIndex:(int)columnIdx { 252 | return sqlite3_column_int64([_statement statement], columnIdx); 253 | } 254 | 255 | - (unsigned long long int)unsignedLongLongIntForColumn:(NSString*)columnName { 256 | return [self unsignedLongLongIntForColumnIndex:[self columnIndexForName:columnName]]; 257 | } 258 | 259 | - (unsigned long long int)unsignedLongLongIntForColumnIndex:(int)columnIdx { 260 | return (unsigned long long int)[self longLongIntForColumnIndex:columnIdx]; 261 | } 262 | 263 | - (BOOL)boolForColumn:(NSString*)columnName { 264 | return [self boolForColumnIndex:[self columnIndexForName:columnName]]; 265 | } 266 | 267 | - (BOOL)boolForColumnIndex:(int)columnIdx { 268 | return ([self intForColumnIndex:columnIdx] != 0); 269 | } 270 | 271 | - (double)doubleForColumn:(NSString*)columnName { 272 | return [self doubleForColumnIndex:[self columnIndexForName:columnName]]; 273 | } 274 | 275 | - (double)doubleForColumnIndex:(int)columnIdx { 276 | return sqlite3_column_double([_statement statement], columnIdx); 277 | } 278 | 279 | - (NSString*)stringForColumnIndex:(int)columnIdx { 280 | 281 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 282 | return nil; 283 | } 284 | 285 | const char *c = (const char *)sqlite3_column_text([_statement statement], columnIdx); 286 | 287 | if (!c) { 288 | // null row. 289 | return nil; 290 | } 291 | 292 | return [NSString stringWithUTF8String:c]; 293 | } 294 | 295 | - (NSString*)stringForColumn:(NSString*)columnName { 296 | return [self stringForColumnIndex:[self columnIndexForName:columnName]]; 297 | } 298 | 299 | - (NSDate*)dateForColumn:(NSString*)columnName { 300 | return [self dateForColumnIndex:[self columnIndexForName:columnName]]; 301 | } 302 | 303 | - (NSDate*)dateForColumnIndex:(int)columnIdx { 304 | 305 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 306 | return nil; 307 | } 308 | 309 | return [_parentDB hasDateFormatter] ? [_parentDB dateFromString:[self stringForColumnIndex:columnIdx]] : [NSDate dateWithTimeIntervalSince1970:[self doubleForColumnIndex:columnIdx]]; 310 | } 311 | 312 | 313 | - (NSData*)dataForColumn:(NSString*)columnName { 314 | return [self dataForColumnIndex:[self columnIndexForName:columnName]]; 315 | } 316 | 317 | - (NSData*)dataForColumnIndex:(int)columnIdx { 318 | 319 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 320 | return nil; 321 | } 322 | 323 | const char *dataBuffer = sqlite3_column_blob([_statement statement], columnIdx); 324 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 325 | 326 | if (dataBuffer == NULL) { 327 | return nil; 328 | } 329 | 330 | return [NSData dataWithBytes:(const void *)dataBuffer length:(NSUInteger)dataSize]; 331 | } 332 | 333 | 334 | - (NSData*)dataNoCopyForColumn:(NSString*)columnName { 335 | return [self dataNoCopyForColumnIndex:[self columnIndexForName:columnName]]; 336 | } 337 | 338 | - (NSData*)dataNoCopyForColumnIndex:(int)columnIdx { 339 | 340 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 341 | return nil; 342 | } 343 | 344 | const char *dataBuffer = sqlite3_column_blob([_statement statement], columnIdx); 345 | int dataSize = sqlite3_column_bytes([_statement statement], columnIdx); 346 | 347 | NSData *data = [NSData dataWithBytesNoCopy:(void *)dataBuffer length:(NSUInteger)dataSize freeWhenDone:NO]; 348 | 349 | return data; 350 | } 351 | 352 | 353 | - (BOOL)columnIndexIsNull:(int)columnIdx { 354 | return sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL; 355 | } 356 | 357 | - (BOOL)columnIsNull:(NSString*)columnName { 358 | return [self columnIndexIsNull:[self columnIndexForName:columnName]]; 359 | } 360 | 361 | - (const unsigned char *)UTF8StringForColumnIndex:(int)columnIdx { 362 | 363 | if (sqlite3_column_type([_statement statement], columnIdx) == SQLITE_NULL || (columnIdx < 0)) { 364 | return nil; 365 | } 366 | 367 | return sqlite3_column_text([_statement statement], columnIdx); 368 | } 369 | 370 | - (const unsigned char *)UTF8StringForColumnName:(NSString*)columnName { 371 | return [self UTF8StringForColumnIndex:[self columnIndexForName:columnName]]; 372 | } 373 | 374 | - (id)objectForColumnIndex:(int)columnIdx { 375 | int columnType = sqlite3_column_type([_statement statement], columnIdx); 376 | 377 | id returnValue = nil; 378 | 379 | if (columnType == SQLITE_INTEGER) { 380 | returnValue = [NSNumber numberWithLongLong:[self longLongIntForColumnIndex:columnIdx]]; 381 | } 382 | else if (columnType == SQLITE_FLOAT) { 383 | returnValue = [NSNumber numberWithDouble:[self doubleForColumnIndex:columnIdx]]; 384 | } 385 | else if (columnType == SQLITE_BLOB) { 386 | returnValue = [self dataForColumnIndex:columnIdx]; 387 | } 388 | else { 389 | //default to a string for everything else 390 | returnValue = [self stringForColumnIndex:columnIdx]; 391 | } 392 | 393 | if (returnValue == nil) { 394 | returnValue = [NSNull null]; 395 | } 396 | 397 | return returnValue; 398 | } 399 | 400 | - (id)objectForColumnName:(NSString*)columnName { 401 | return [self objectForColumnIndex:[self columnIndexForName:columnName]]; 402 | } 403 | 404 | // returns autoreleased NSString containing the name of the column in the result set 405 | - (NSString*)columnNameForIndex:(int)columnIdx { 406 | return [NSString stringWithUTF8String: sqlite3_column_name([_statement statement], columnIdx)]; 407 | } 408 | 409 | - (void)setParentDB:(FMDatabase *)newDb { 410 | _parentDB = newDb; 411 | } 412 | 413 | - (id)objectAtIndexedSubscript:(int)columnIdx { 414 | return [self objectForColumnIndex:columnIdx]; 415 | } 416 | 417 | - (id)objectForKeyedSubscript:(NSString *)columnName { 418 | return [self objectForColumnName:columnName]; 419 | } 420 | 421 | 422 | @end 423 | -------------------------------------------------------------------------------- /SQLiteApp/Vendor/fmdb/MDatabase-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // MDatabase-Bridging-Header.h 3 | // MDatabase 4 | // 5 | // Created by iDevFans on 16/8/28. 6 | // Copyright © 2016年 http://www.macdev.io All rights reserved. 7 | // 8 | 9 | #ifndef MDatabase_Bridging_Header_h 10 | #define MDatabase_Bridging_Header_h 11 | 12 | #import "FMDatabaseQueue.h" 13 | #import "FMDatabase.h" 14 | 15 | #endif /*MDatabase_Bridging_Header_h */ 16 | -------------------------------------------------------------------------------- /SQLiteAppLogicArchitecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SQLiteAppLogicArchitecture.png -------------------------------------------------------------------------------- /Schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/Schema.png -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/project.xcworkspace/xcuserdata/idevfans.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SwiftSQLiteApp.xcodeproj/project.xcworkspace/xcuserdata/idevfans.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/project.xcworkspace/xcuserdata/zhaojw.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/javaliker/SwiftSQLiteApp/dcd5deee4367b49679fee054369445a8be35c283/SwiftSQLiteApp.xcodeproj/project.xcworkspace/xcuserdata/zhaojw.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/xcuserdata/idevfans.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 8 | 14 | 15 | 16 | 18 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/xcuserdata/idevfans.xcuserdatad/xcschemes/SwiftSQLiteApp.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/xcuserdata/idevfans.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftSQLiteApp.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 712EF7D41D8034BC006B6DB9 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/xcuserdata/zhaojw.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /SwiftSQLiteApp.xcodeproj/xcuserdata/zhaojw.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SwiftSQLiteApp.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | --------------------------------------------------------------------------------