首先要知道 SQLite 只支持如下五种存储类型: 存储类 描述 NULL 值是一个 NULL 值。 INTEGER 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。 REAL 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。 TEXT 值是一个文本字符串,使用数据库编码格式(默认为 UTF-8)存储。 BLOB 值是一个二进制数据,完全根据它的输入存储。 SQLite.swift 将这五种存储类型与 Swift 数据类型做了一一对应: Swift Type SQLite Type Int64 INTEGER Double REAL String TEXT nil NULL SQLite.Blob BLOB 其中 SQLite.Blob 类型是 SQLite.swift 自己定义的类型。另外,对于 Swift 的 Bool 类型,SQLite.swift 会将其转换成 Int64 然后以 INTEGER 类型进行存储(0 表示 false, 1 表示 true);对于 Swift 的 Date 类型,SQLite.swift 会将其转换成 String 类型后,通过 TEXT 类型进行存储;Int 类型也会自动转换成 Int64 后进行存储。 如果我们想定义其他的 Swift 数据类型到 SQLite 存储类型的转换,就得实现扩展 Swift 数据类型,使其满足 SQLite.Value 协议。Value 协议的作用就是将 Swfit 数据类型 A 转换成上述五种基本的 Swfit Type 之一,然后在存储时自动转换成对应的 SQLite Type 进行存储。 以 Swift 的 UUID 类型为例,最简单的做法就是将其转化成字符串进行存储。更“困难”一点的,也是本文主要想描述的,就是将 UUID 类型转换成 SQLite.Blob 类进行存储。 import SQLite struct Book { var id: Int64 var uuid: UUID var title: String var readed: Bool var read_at: Date? } // 扩展 UUID,使其满足 SQLite.Value 协议 // 实现 UUID <==> SQLite.Blob 的转换 extension UUID: Value { // 它的目标类型是 SQLite.Blob public typealias Datatype = Blob public static var declaredDatatype: String { return Blob.declaredDatatype } // 将目标类型转换成 UUID // SQLite.Blob 类型有个 bytes: [UInt8] 的存储属性,存储着实际的二进制数据 public static func fromDatatypeValue(_ datatypeValue: Datatype) -> UUID { let bytes = datatypeValue.bytes return UUID(uuid: (bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15])) } // 将 UUID 类型转换成目标类型 // 可以通过 UUID.uuid 计算属性获取其二进制值的表示,包含 16 个 UInt8 数据的元组:(UInt8, UInt8, ...) public var datatypeValue: Datatype { var bytes = [UInt8](repeating: 0, count: 16) (bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]) = self.uuid return Datatype(bytes: bytes) } } /** * 以下是测试代码 */ #if DEBUG db.trace { print("execute…
NSWindow 无标题栏窗口无法获得焦点与 resize 鼠标指示的解决办法
Base on macOS 10.15, Xcode 11.7. 在 macOS 程序中,对 NSWindow 实例可以通过删除 NSWindow.StyleMask.titled 属性来设置窗口为无标题栏窗口。 // 方法一:实例化时通过 styleMask 实参数组设置,不包括 .titled 即无标题栏 window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), styleMask: [.resizable], backing: .buffered, defer: false) // 方法二:或者在后面通过 remove 掉该属性也行 window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) window.styleMask.remove(.titled) 但是无标题栏的窗口,其 NSWindow.canBecomeKey 属性会变成 false。Key window 的意思,就是指可以接收 keyboard 事件的窗口,所以如果一个 NSWindow 的 canBecomeKey 属性为 false,就说明它不能接收键盘事件,即无法获得输入焦点。同时如果鼠标放在窗口的边框上,也不会出现缩放窗口的指示,虽然我上面定义了 resizable,窗口也确实是可以缩放,但就是没有指示出来。如下图所示: 而且,NSWindow.canBecomeKey 是一个只读的计算属性,由系统自动计算。按 apple 官方文档的说法,如果一个窗口有 title bar 或者 resize bar,那么该属性就会是 true。但实际上,只要没有了 title bar,即使我定义了 resizable,该属性也是 false。这到底是系统的 BUG 还是文档乱写的啊。 (╯▔皿▔)╯ 为了想要实现无标题栏的窗口还能获得输入焦点,并且能显示这个 resize indicator。那么我们就不得不定义一个 NSWindow 的子类,并override canBecomeKey 计算属性: import SwiftUI class MyWindow: NSWindow { override var canBecomeKey: Bool { return true } } 然后用这个子类来实例化一个无标题栏窗口即可。 思考🤔 是不是其实无标题栏窗口,应该用 NSPanel 来实现更好点啊?跟NSWindow 有啥区别。后面试试
Carthage 依赖管理工具的使用
快速上手 1. 安装 carthage brew install carthage 2. 创建依赖文件 在项目根目录下创建 Cartfile 文件。在文件中写入依赖,比如: github "Alamofire/Alamofire" ~> 4.7.2 3. 下载并编译依赖库 在项目根目录下执行 carthage update 命令,Carthage 会根据 Cartfile 文件下载并编译依赖库。运行后生成 Cartfile.resolved 文件与 Carthage 目录。 # 使用 --cache-builds 参数,可以令 Carthage 不重复编译已编译过的库 carthage update --cache-builds # 使用 --platform 参数,指定要编译的目标平台 carthage update --platform macOS, iOS # 设置下载代理 # 设置临时环境变量 https_proxy,该变量只对跟在后面执行的那一句脚本起作用 https_proxy=http://127.0.0.1:1984 carthage update # 设置 Xcode 版本 # 设置临时环境变量 DEVELOPER_DIR,并执行 carthage update 命令 DEVELOPER_DIR=/path/to/Xcode-beta.app/Contents/Developer carthage update 4. 在编译生成的 .framework 手工添加到项目 打开 Xcode,进入项目的 Targets 配置,在 General 面板,将编译后生成在 Carthage/Build/<platform>/ 目录下的 .framework 拖到这个 "Frameworks, Libraries and Embedded Content" 列表中。 然后在打开 Build Phases 面板,点击左上角的 + 号,选择 "New Run Script Phase"。然后将下面这行代码添加到 Run Script 中: /usr/local/bin/carthage copy-frameworks 在 Input Files 中添加上要加入的依赖库.framework。 5. OK,可以在代码中使用第三方库啦。 查看帮助文档 # 查看 carthage 所有命令 carthage help # 查看 update 命令支持的所有参数 carthage help update 使用代理进行下载 如果系统环境变量中存在 http_proxy 与 https_proxy 这两个变量,那么 Carthage 会自动使用这两个环境变量对相应协议的依赖库进行下载。不过一般情况下我们要下载的依赖库都是 github 上的,其下载路径都是 https 协议,所以一般就只需要设置 https_proxy 环境变量即可。 # 先在该终端中导入 https_proxy 环境变量,注意 export 导入的环境变量只在当前会话有效 export https_proxy=http://127.0.0.1:1984 # 然后执行 carthage 下载命令 carthage update 如果嫌上面要写两次命令太麻烦的话,可以简写成: # 设置临时环境变量 https_proxy,该变量只对跟在后面执行的那一句脚本起作用 https_proxy=http://127.0.0.1:1984 carthage update 不重复编译 默认情况下,每次执行 carthage update,它都会重新编译依赖库,这很麻烦。 # 使用 --cache-builds 参数,可以令 Carthage 不重复编译已编译过的库 carthage update --cache-builds 切换 Xcode 版本 如果说电脑上装有多个版本的 Xcode,比如一个正式版,一个 beta 版。那么默认的命令行环境使用的是正式版的 xcodebuild。可以通过一下命令检查: # 检查 xcodebuild 版本 xcodebuild -version # 检查命令行环境的 xcode 路径 xcode-select -p 可以通过 xcode-select 来切换默认的 xcode 路径: # 切换默认的 xcode 到 beta 版 sudo xcode-select -s /path/to/Xcode-beta.app/Contents/Developer # 然后再执行 Carthage 操作 carthage update 但是 xcode-select 切换并不是临时的,想用回之前的版本还得手工切回去。如果只是想临时使用另一个版本的 xcode。应该使用命令前置的临时环境变量设置。 # 设置临时环境变量 DEVELOPER_DIR,并执行 carthage update 命令 DEVELOPER_DIR=/path/to/Xcode-beta.app/Contents/Developer carthage update
SwiftUI 入门 — 系统状态栏程序
基于 Xcode Version 11.5 1. 新建项目 输入项目名称,选择 SwiftUI,选择 Core Data。 因为用到了 Core Data,这里需要修改自动生成的 AppDelegate.swift 中的一行 BUG 语句(升级到 Xcode 11.6 了依然存在。。) // let contentView = ContentView().environment(\.managedObjectContext, persistentContainer.viewContext) // 注释掉上面这句,修改为如下两句 👇 let context = persistentContainer.viewContext let contentView = ContentView().environment(\.managedObjectContext, context) 2. 在 ContentView 新增“退出”按钮 struct ContentView: View { var body: some View { VStack { Text("Hello, World!") .frame(maxWidth: .infinity, maxHeight: .infinity) Button("Quit"){ NSApplication.shared.terminate(self) } } } } 运行该程序会显示如下窗口,点击 Quit 按钮可以直接退出程序。 3. 将程序图标追加到系统状态栏 3.1 新增图标 点击项目面板的 Assets.xcassets 图标,然后在编辑面板的左边点击右键,选择 “New Image Set”。 在新建的 Image Set 的 Attributes inspector 面板中,修改图片集的名字与可选尺寸。然后将一个 png 图标拖动到 image 面板的虚线框。 3.2 添加到系统状态栏 NSStatusBar 在项目根目录下新建一个 StatusBarController.swift 文件: import AppKit class StatusBarController { private var statusItem: NSStatusItem private var statusBarButton: NSStatusBarButton init() { statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength) statusBarButton = statusItem.button! // 设置状态栏图标与标题 statusBarButton.image = NSImage(imageLiteralResourceName: "StatusBarIcon") statusBarButton.image!.size = NSSize(width: 18, height: 18) statusBarButton.imagePosition = .imageLeft statusBarButton.title = "倒计时" // 设置状态栏图标点击事件 // #selector(func) 语法糖生成一个 Selector 实例,它对应 Object-C 的 SEL 类型,实际上就是“函数指针” statusBarButton.action = #selector(statusBarButtonClicked(_:)) statusBarButton.target = self } // Swift 中的 @objc 特性表示表示这个声明可以被 Object-C 代码调用 @objc func statusBarButtonClicked(_ sender: Any?) { print("status bar button clicked") } } 然后在 AppDelegate 类中添加存储属性:let statusBar = StatusBarController()。 编译运行程序,在系统状态栏就会多出一个程序图标,点击图标即会在 Xcode 的控制台打印 "status bar button clicked"。 4. 隐藏 Dock 图标与关闭程序主窗口 4.1 隐藏 Dock 图标 在 Xcode 中打开 Info.plist,右键点击“ Add Row”。新建一行配置:Application is agent (UIElement) 并设置值为 YES。然后重新编译运行,就会发现 Dock 栏不再出现该程序图标。 4.2 关闭主窗口 打开 AppDelegate.swift,将默认的生成窗口被设置主视图的那段代码删除即可: // 删除下面这段代码 window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 300), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) window.center() window.setFrameAutosaveName("Main Window") window.contentView = NSHostingView(rootView: contentView) window.makeKeyAndOrderFront(nil) 这时候编译运行会有一个 Warning 提示 let contentView 常量定义了却没有被使用。 5. 显示弹出视图 此时我们运行程序,什么都没有只有一个状态栏图标。我们想要的是点击这个状态栏图标能弹出一个程序视图,为此,我们需要生成一个 NSPopover 实例,并将 ContentView 绑定到该 NSPopover…
SwiftUI 入门
SwiftUI 的官方入门教程:Creating and Combining Views 不得不说我觉得这个教程看起来很好但是实际真不咋滴,只说怎么用,不说为什么这么用,根本不考虑初学者的感受。另外该教程应该是 WWDC 2019 发布的,基于 Xcode 11。而在最新发布的 Xcode 12 beta 版本中,似乎 SwiftUI 有了自己的生命周期,不再依赖于 UIKit。 1. 项目结构 1.1 iOS 项目初始结构 在 Xcode 11.5 中新建一个 "iOS" - "Single View App" 项目,然后在 Xcode 左侧的 "Project Navigator" 可以看到项目的目录结构如下图所示: • AppDelegate.swift 文件负责整个应用程序的生命周期。 在代码中可以看到对 AppDelegate 类使用了 @UIApplicationMain 特性(注意,我们通常将 Swift Attribute 翻译做“特性”,以与“属性” Property 区别开),该特性表示应用程序入口。 从 Delegate 这个名字可以看出它只是一个代理,底层还是依赖于 UIKit。 • SceneDelegate.swift 负责应用程序的场景显示。 AppDelegate 通过 Info.plist 中定义的 "Application Scene Manifest" 来找到启动场景,然后再由 SceneDelegate 来创建视图。我们可以在 SceneDelegate.swift 的代码中看到它创建了一个 ContentView 实例并设置为该场景的根视图控制器。 参考:iOS13 Scene Delegate详解 • ContentView.swift 就是要展示给用户看到的视图。对于 iOS 程序而言,应该是 App 》 Scene 》 Window 》 View。而对于 macOS 程序而言,就没有 Scene 在中间插一腿了(或者其实应该说是 macOS 程序只有一个默认场景)。 • Assets.xcassets 是资源目录,存放图标、图片等资源 • LaunchScreen.storyboard 是应用程序的启动界面 • Info.plist 是应用程序的配置属性,包括程序的版本、支持的设备、需要的系统权限等等。 • Preview Content 目录存放那些在 Preview 中要用到的而又不在主目录中的静态资源。有点像是 “mock” 资源,因为有些图片或者数据是只有程序运行时才获取的,如果想要在 Preview 中预览这些数据,就可以将它们放在这里。 • Products 目录存放的是最后编译生成的应用程序 1.2 macOS 项目初始结构 同样的在 Xcode 中新建一个 "macOS" - "App" 项目,得到的初始项目结构如下图: • AppDelegate.swift 同样的,AppDelegate 也是 macOS 应用程序的入口。不过与 iOS App 不同的是,它用了 @NSApplicationMain 特性,依赖于 AppKit。并且在 applicationDidFinishLaunching() 函数中创建了 window 窗口,并设置根视图为 ContentView。 • ContentView.swift 就是要展示给用户的视图。 • Main.storyboard 文件 • LandmarksMac.entitlements 其实也是一个 plist 文件,定义了应用程序需要用到的系统服务。 2. 生命周期 LifeCycle Xcode 11 版本的 SwiftUI 程序,我们可以在 AppDelegate.swift 文件中看出其生命周期其实是依赖于 UIKit (iOS App) 或者 AppKit (macOS App) 的。不知道未来正式发布的 Xcode 12 中的 SwfitUI 是否能彻底抛弃这些底层依赖。 3. 视图 View SwiftUI 的核心概念应该就是视图了,它表示应用程序的用户界面。不管是自定义界面,还是文本、按钮、图标等这些基本 UI 控件也都必须满足 View 协议。 Show me the code,我们来看一下默认项目的视图代码吧。 import SwiftUI struct ContentView: View { var body: some View { Text("Hello, World!") } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } } 这里面定义了两个结构体,一个是真正的视图 ContentView,一个是视图预览 ContentView_Previews。 struct ContentView: View 这一行很简单,表示 ContentView 实现 View 协议。(Swfit 语言中的协议 Protocol 等同于其他语言中的接口类型) var body: some View { Text("Hello, World!") } 表示 body 是一个只读计算属性。some 关键字表示其类型是一个 Opaque Types,它要求 getter 函数返回一个满足 View 协议的实例。后面花括号是简化的 getter 函数体,展开的话应该是如下形式: struct ContentView: View { var body: some View…
在swift项目中使用AFNetworking网络库
关于AFNetworking的介绍就不多说了,这是一个时下比较流行的ios和mac os网络库,不过遗憾的现在只有objective-c版本。 1. 安装CocoaPods $ sudo gem install cocoapods 安装过程比较慢,你可以用$ sudo gem install cocoapods -V来观察后台的执行过程。 另外,由于GFW的原因,RubyGems资料源可能被墙,可以将gem的源换成淘宝的RubyGems镜像。 2. 编辑Podfile 在项目目录下添加podfile文件,编辑内容: source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' pod 'AFNetworking', '~> 2.5.0' 在终端中执行: $ pod install 然后会在项目目录下生成<YourProjectName>.xcworkspace文件,之后就使用该xcworkspace文件来打开项目。 3. 在项目中引用AFNetworking 双击上面生成的xcworkspace文件,打开xcode 3.1 在项目目录中添加一个iOS Header File,可以命名为<YourProjectName>BridgingHeader.h #ifndef AFNetworkingTest_BridgingHeader_h #define AFNetworkingTest_BridgingHeader_h #import <AFNetworking/AFNetworking.h> #endif 3.2 在项目的Build Settings中,搜索Objective-C Bridging Header设置,定义为YourProjectName/YourBridgingHeader.h,如下图所示 3.3 现在,可以在你的swift代码中添加一句 let manager = AFHTTPRequestOperationManager() 编译通过! 4. 使用方法
[转载]ios app登录/注册模块的实现:swift+xcode6+ios8+json
原文地址:http://dipinkrishna.com/blog/2014/07/login-signup-screen-tutorial-xcode-6-swift-ios-8-json/ 最近我自己在学习swift,看了一些国内的视频教程(幕课网 跟 SwiftV课堂)。但是一直没有找到关于前后台通信的教程,其中最基础的应该就是app的登录注册模块了,除了ui的实现,最重要的是跟后台的交互。 这篇老外的教程就挺好的,而且还给了简单的后台代码。其基本架构就是: 1. swift实现前台ui,向后台发起post请求,并响应后台返回的json数据 2. php + mysql实现后台,处理请求并返回json数据 翻译什么的就算了,由于老外的视频都放youtube上,结果你懂的。所以我在优酷上复制了一份,原文如下: This tutorial will guide you to create a simple app with a Signup and Login screen which takes username and password from the user and then posts it to an url and parse the JSON response. We will have three view controllers for this project, Signup, Login and Home. Create a New project. And add the Screens and Segues/transitions. Home – Check for an existing session, else goto Login Login Screen – Post data to URL and parse the JSON response. Signup Screen – Post data to URL and parse the JSON response. Add Logout to Home Screen 1. Create a New project. And add the Screens and the Segue/transition. I had issues with Xcode 6.1, not allowing me to create segue between View Controllers. Please see this video for an alternative way to do that: 2. Create Classes Properties And Methods: Add code to viewDidAppear of HomeVC.swift to check for existing login, if no session is found then show the login screen. override func viewDidAppear(animated: Bool) { super.viewDidAppear(true) let prefs:NSUserDefaults = NSUserDefaults.standardUserDefaults() let isLoggedIn:Int = prefs.integerForKey("ISLOGGEDIN") as Int if (isLoggedIn != 1) { self.performSegueWithIdentifier("goto_login", sender: self) } else { self.usernameLabel.text = prefs.valueForKey("USERNAME") as NSString } } signupTapped in SignupVC.swift: @IBAction func signupTapped(sender : UIButton) { var username:NSString = txtUsername.text as NSString var password:NSString = txtPassword.text as NSString var confirm_password:NSString = txtConfirmPassword.text as NSString if ( username.isEqualToString("") || password.isEqualToString("") ) { var alertView:UIAlertView = UIAlertView() alertView.title = "Sign Up Failed!" alertView.message…