2015-03-16 17:14:42 diveinedu 阅读数 36
iOS8-Swift开发教程—41426人已学习
课程介绍    
201503161012026345.jpg
    共有60个课程,教程由浅入深 生动活泼的讲述了Swift的各种应用 Swift是一门用于开发iOS和OSX应用程序的新语言,基于C和Object-C,但是没有C兼容性的限制。Swift采用安全的编程模式,并增加了许多新的现代模式,让编程更加的容易、灵活,让编程更加有乐趣。Swift被目前成熟并很受欢迎的Cocoa和CocoaTouch支持。
课程收益
    教程展示Swift各种应用,给你足够的信息来学习Swift。如果在学习本节时候你有不明白的地方不用担心,整个教程的其余部分会有一个详细的说明。
讲师介绍
    李珊更多讲师课程
    中科院研究生毕业,从 2009 年开始从事 iOS 开发,项目实战经验丰富。曾为中南大学软件学院等硕士研究生进行实战项目培训,从 2012 年开始在戴维营教育负责 iOS 开发培训,积累了大量的授课经验。所带学生现均供职于大型互联网公司或企业,应届毕业生均达到 7K 以上。对学生要求严格,讲解通俗易懂,广受好评。
课程大纲
    1.iOS8-Swift-ObjectiveC混编开发Framework  18:29
    2.iOS8开发:第一个Swift应用-编程初体验  14:21
    3.iOS8开发:第二个Swift应用-加法计算器  22:49
    4.iOS8开发:第三个Swift应用-四则运算计算器  32:11
    5.iOS8开发:Swift编程-流程控制-For循环  34:12
    6.iOS8开发:Swift编程-流程控制-Switch语句(一)  16:14
    7.iOS8开发:Swift编程-流程控制-Switch语句(二)  25:11
    8.iOS8开发:Swift编程-流程控制-Switch语句(三)  27:18
    9.iOS8开发:第四个Swift应用-登录界面  29:55
    10.iOS8开发:第五个Swift应用-学生成绩  23:33
    11.iOS8开发:第六个Swift应用-签到  25:51
    12.iOS8开发:第七个Swift应用-记事本(一)  26:42
    13.iOS8开发:第七个Swift应用-记事本(二)  27:43
    14.iOS8开发:第七个Swift应用-记事本(三)  17:05
    15.iOS8开发:第七个Swift应用-记事本(四)  8:43
    16.iOS8开发:第七个Swift应用-记事本(五)  17:56
    17.iOS8开发:第七个Swift应用-记事本(六)  12:19
    18.iOS8开发:Swift编程-数据类型-Optional  30:50
    19.iOS8开发:Swift编程-数据类型-类和对象(一)  12:44
    20.iOS8开发:Swift编程-数据类型-类和对象(二)  9:03
    21.iOS8开发:Swift编程-数据类型-类和对象(三)  11:10
    22.iOS8开发:Swift编程-数据类型-类和对象(四)  22:25
    23.iOS8开发:Swift编程-数据类型-类和对象(五)  8:45
    24.iOS8开发:Swift编程-UI控件-UIView(一)  27:24
    25.iOS8开发:Swift编程-UI控件-UIView(二)  13:38
    26.iOS8开发:Swift编程-UI控件-UIView(三)  2:53
    27.iOS8开发:Swift编程-数据类型-枚举(一)  17:09
    28.iOS8开发:Swift编程-数据类型-枚举(二)  13:21
    29.iOS8开发:Swift编程-UI控件-UIButton(一)  19:26
    30.iOS8开发:第八个Swift应用-流媒体直播世界杯(一)  29:37
    31.iOS8开发:第八个Swift应用-流媒体直播世界杯(二)  6:19
    32.iOS8开发:Swift编程-数据类型-协议(一)  16:24
    33.iOS8开发:Swift编程-UI控件-UITableView(一)  29:41
    34.iOS8开发:Swift编程-UI控件-UITableView(二)  12:39
    35.iOS8开发:Swift编程-数据类型-扩展(一)  11:07
    36.iOS8开发:Swift编程-数据类型-闭包(一)  15:35
    37.iOS8开发:Swift编程-数据类型-闭包(二)  16:13
    38.iOS8开发:Swift编程-UI控件-UIView动画(一)  22:38
    39.iOS8开发:Swift编程-数据持久化-文件读写(一)  29:31
    40.iOS8开发:Swift编程-UI控件-WebView(一)  17:08
    41.iOS8开发:Swift编程-UI控件-TabBarController(一)  11:30
    42.iOS8开发:Swift编程-UI控件-TabBarController(二)  25:29
    43.iOS8开发:Swift编程-UI控件-SegmentedControl  18:42
    44.iOS8开发:Swift编程-UI控件-UISlider  18:43
    45.iOS8开发:Swift编程-UI控件-UISwitch  13:43
    46.iOS8开发:Swift编程-UI控件-UIProgressView  16:33
    47.iOS8开发:Swift编程-UI控件-UIPageControl  18:32
    48.iOS8开发:Swift编程-UI控件-UIStepper  10:48
    49.iOS8开发:Swift编程-UI控件-ActionSheet  22:32
    50.iOS8开发:Swift编程-基础框架-网络(一)  40:49
    51.iOS8开发:Swift编程-基础框架-网络(二)  21:59
    52.iOS8开发:Swift编程-多媒体-AVAudioPlayer  36:22
    53.iOS8开发:Swift编程-iOS应用-沙盒模型  37:43
    54.iOS8开发:Swift编程-基础框架-文件管理(一)  39:08
    55.iOS8开发:第九个Swift应用-网络音乐播放器(一)  29:21
    56.iOS8开发:第九个Swift应用-网络音乐播放器(二)  21:03
    57.iOS8开发:第九个Swift应用-网络音乐播放器(三)  31:57
    58.iOS8开发:第九个Swift应用-网络音乐播放器(四)  42:14
    59.iOS8开发:第九个Swift应用-网络音乐播放器(五)  35:08
    60.iOS8开发:第九个Swift应用-网络音乐播放器(六)  20:25
大家可以点击【查看详情】查看我的课程
2016-01-03 17:51:22 u011349387 阅读数 7881

原文:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1

翻译原文:http://blog.csdn.net/kmyhy/article/details/45150649


打开Xcode,使用 File\NewProject… 菜单,在弹出窗口中选择 “OS X/Application”,然后Next。


在接下来的窗口中,配置App信息。在product name栏中输入ScaryBugsMac,输入你的机构名以及机构ID。剩余字段保留为空白。

选择Swift作为开发语言,保持所有选项框反选,document extension栏保留为空白。然后点Next。


然后Xcode会要求你选择项目保存路径。选择一个物理路径,然后点击Create。

项目就创建完了,这是一个单窗口App。点击工具栏左上角的Run按钮,运行这个程序,效果如下图所示。


首先我们来总结一下。我们使用Xcode模板创建了一个Mac App项目,然后编译运行了这个空白项目。与iOS开发的最大不同在于:

·      窗口不需要特别指明大小,比如iPhone或iPad屏幕大小——MacApp的窗口是可以通过拖动来改变大小的。

·      Map App可以拥有多个窗口,窗口支持最小化,重排等操作。

然后我们来新建一个View Controller,并在它上面放入App的主界面。使用

 File\New\File… 菜单,在弹出窗口中,选择 OS X\Source\Cocoa Class,然后点Next。


类名填入 MasterViewController, “Subclass of”填入NSViewController。确保“Also create XIB file for user interface” 为勾选,然后点Next。


在最后一个弹出窗口中,点击Create。新的View Controller将显示在项目导航窗口中:


打开MasterViewController.xib。需要注意的是,在Mac App中,有大量的类和iOS中都类似,只不过是以NS前缀命名。例如NSScrollView、NSLabel、NSButton等。

在右下角的UI Controls面板(位于第三个Tab)中,选中NSTableView将它拖到MasterViewController.xib的画布中。


不要担心Table View的大小,我们待会会来处理它。


打开 AppDelegate.swif在window属性下面插入如下语句:

var masterViewController: MasterViewController!

 

找到 applicationDidFinishLaunching 方法,这个方法在App启动时调用。

注意: 这个方法等同于iOS中的application(_:didFinishLaunchingWithOptions:)方法。

applicationDidFinishLaunching方法内,加入以下语句:

masterViewController = MasterViewController(nibName:"MasterViewController", bundle: nil)

window.contentView.addSubview(masterViewController.view)

masterViewController.view.frame = (window.contentView asNSView).bounds

 

在 OS X中,窗口(NSWindow对象)总是有一个默认的View,即contentView。它自动占据整个窗口的大小。当我们想在窗口中使用自己的视图时,需要用addSubview方法将它添加到contentView的subviews中。

在iOS开发中,我们可以设置将一个View Controller直接设置为窗口的rootViewController属性,但在OS X中你只能将视图添加到contentView的subviews,因为OS X中没有rootViewController的概念。

运行App,你将看到如下画面:


数据模型

接下来创建数据模型。

首先我们来熟悉一下Xcode项目文件的组织结构:


默认模板会创建一个以项目名称为名的文件夹。在这个文件夹下有一个supporting files的子文件夹,其中存放plist和资源文件。当项目很大时,会创建大量的文件,查找文件就会变得很困难。因此我们需要有一个良好的项目文件组织形式。

首先,我们新建一个文件夹(group),命名为GUI。在ScaryBugsMac文件夹上点击右键,将弹出一个快捷菜单,选择NewGroup,然后输入GUI。

然后将所有跟UI有关的文件拖到这个文件夹( AppDelegate.swiftMasterViewController.swift/.xiband MainMenu.xib),如下图所示:


然后新建另一个文件夹Model。


在Model文件夹中将包含如下内容

·      ScaryBugData: 包含两个属性:昆虫的名称及昆虫的估价。

·      ScaryBugDoc: 包含3个属性:昆虫图片、昆虫缩略图及一个ScaryBugData属性。

实现模型对象

注意: 如果你阅读过 How ToCreate A Simple iPhone App on iOS 5 Tutorial, 你会发现接下来的内容和那篇教程中的相应内容几乎一模一样。这是因为Mac和iOS编程大部分SDK都是系统的,除了UI和操作系统相关的API。而模型对象不涉及UI,因此模型对象的代码基本是一致的。

对于ScaryBug的模型类,将Mac版本与iOS版本只有一个地方不同,即将UIImage类修改为NSImage即可。当然,你也需要将它从O-C实现修改为Swift实现。

在Model文件夹上点击右键,点击 “New File…”,然后选择OS X\Source\Cocoa Class 模板,然后点击Next。


类名输入 ScaryBugData, Subclass of 输入 NSObject ,点击 Next。


在最后一个弹出界面中,点击Create。项目导航窗口将显示如下:


打开ScaryBugData.swift 替换为如下内容:

import Foundation  

class ScaryBugData: NSObject {

   var title: String

   var rating: Double  

   override init() {

    self.title = String()

    self.rating = 0.0  

}

init(title: String, rating: Double) {

     self.title = title

     self.rating = rating  

}

}

然后创建另一个模型对象ScaryBugDoc

打开ScaryBugDoc.swift 编辑为如下内容:

import Foundation

import AppKit  

class ScaryBugDoc: NSObject {

   var data: ScaryBugData

   var thumbImage: NSImage?

   var fullImage: NSImage?

override init() {

     self.data = ScaryBugData()

   }

init(title: String, rating: Double, thumbImage: NSImage?, fullImage:NSImage?) {

     self.data = ScaryBugData(title: title, rating: rating)

     self.thumbImage = thumbImage

     self.fullImage = fullImage 

 }

}

注意thumbImage 和fullImage声明为可空的 NSImage ,因此他们不需要在默认构造函数中初始化。

打开MasterViewController.swift ,增加一个属性声明:

var bugs = [ScaryBugDoc]()

这个数组属性用于存储昆虫列表,接下来我们将会用一些数据填充这个数组。

填充数据及图片

MasterViewController 需要用一系列昆虫来填充。你可以从此处下载所需的

 昆虫图片

下载完图片之后,,将所有图片从Finder中拖到Images.xcassets中如下图右边AppIcon之下的位置:


打开 MasterViewController.swift 添加如下方法:

func setupSampleBugs() {

   let bug1 = ScaryBugDoc(title: "Potato Bug", rating: 4.0,

     thumbImage:NSImage(named: "potatoBugThumb"), fullImage:NSImage(named: "potatoBug"))

   let bug2 = ScaryBugDoc(title: "House Centipede", rating: 3.0,

thumbImage:NSImage(named: "centipedeThumb"), fullImage:NSImage(named: "centipede"))  

   let bug3 = ScaryBugDoc(title: "Wolf Spider", rating: 5.0,

thumbImage:NSImage(named: "wolfSpiderThumb"), fullImage:NSImage(named: "wolfSpider"))  

   let bug4 = ScaryBugDoc(title: "Lady Bug", rating: 1.0,

thumbImage:NSImage(named: "ladybugThumb"), fullImage:NSImage(named: "ladybug"))

   bugs = [bug1, bug2, bug3, bug4]

}

打开AppDelegate.swift ,找到 applicationDidFinishLaunching方法,在addSubview之前加入以下代码:

masterViewController.setupSampleBugs()

编译运行程序,确保编译通过。

接下来,我们将在UI中显示这些图片和数据。

显示昆虫列表

在 OS X中,Table View使用 NSTableView类,它等同于iOS的UITableView 类,但有一个最大的不同是:NSTableView 的每一行有多个列或多个单元格。

·      OS X 10.7Lion之前,table view cell继承于NSCell类。而后者并非NSView类,因此开发者需要自己处理绘图和鼠标事件。

·       OS X 10.7开始,table view从 NSView继承。这样就和UITableView差不多了。cell也有相应的View类型,因此也和iOS中的类似——这样我们就轻松得多了!

在本教程中,使用的是基于View的TableView。如果你想了解NSTableView的用法,你可以阅读 这里, 它对 table views 的用法进行了详细的说明。

打开MasterViewController.xib ,选中table view。注意Table View位于Scroll View中的Clip View中,因此第一个点击你选中的会是ScrollView,第二次点击你选中的才是ClipView,第三次点击才会选中Table View。

当然,你也可以直接从IB的Objects面板中选择Table View对象(展开 Clip View对象)。

选中Table View之后,在属性面板中,确认Content Mode一项是设置为View Based而不是Cell Based。同时,因为我们的列表仅显示单列,所以将Columns属性修改为1。

勾选 “Alternating Rows”属性,让表格以“明暗颜色交替”的方式绘制单元格。

反选 “Headers” 属性,因为我们不需要在表格上方显示一个标题。

接下来我们修改单元格的大小。选择Table View上的列,拖动它的大小使其占据整个表格宽度。


然后是单元格的配置。我们需要在单元格中显示昆虫的图片和名称,因此需要在Cell中添加一个Image和一个文本控件。

IB中有一种带Image View和Text Field的NSTableCellView对象,我们可以使用它。

在Object library 面板中,找到 “Image & Text Table Cell View”, 将它拖到Table View中。


在Table View中,将原来的cell删除(用delete键)。

选中Table View Cell,在Size面板中,将高度调整为32。

然后选中Image View和 Text Field,使它们位于单元格中心,并调整ImageView和Text Field的大小,使它们看起来如下图所示:


接下来要为每一列设置一个id。当然对于本教程来说,我们只有一个列,因此列id可能不是必须的。

在Objects面板中选择表格列,打开Identity面板,将Identifier设置为BugColumn。


如同在iOS中一样,Table View也有Data Source和Delegate属性。正常情况下,这两个属性都是同一个对象,即 MasterViewController

选择Table View,打开Connections面板,在Outlets一项下找到delegate和data source。

点击delegate右边的小圆圈,拖到Objects面板中的“File’s Owner”上。

这将吧Table View 的delegate 属性设置为 MasterViewController。重复同样的动作,设置Data Source属性。

最终如下图所示:

打开 MasterViewController.swift 将下列代码放在文件最后:

// MARK: - NSTableViewDataSource

extension MasterViewController: NSTableViewDataSource {

   func numberOfRowsInTableView(aTableView: NSTableView!) -Int {

     return self.bugs.count  

}  

   func tableView(tableView: NSTableView!, viewForTableColumn tableColumn: NSTableColumn!, row: Int) -NSView! {

     // 1

var cellView: NSTableCellView =tableView.makeViewWithIdentifier(tableColumn.identifier, owner: self) asNSTableCellView      

// 2    

if tableColumn.identifier == "BugColumn" {

    // 3  

    let bugDoc = self.bugs[row]      

    cellView.imageView!.image = bugDoc.thumbImage

    cellView.textField!.stringValue = bugDoc.data.title

    return cellView    

}      

return cellView  

   }

}  

// MARK: - NSTableViewDelegate

extension MasterViewController: NSTableViewDelegate { }

我们通过扩展让MasterViewController 采用NSTableViewDelegate 和NSTableViewDataSource协议。

要让列表渲染数据至少需要实现两个数据源方法。

首先是numberOfRowsInTableView 方法,OS通过这个方法获取要渲染的表格行数。

其次是tableView(_:viewForTableColumn:row:)方法。OS通过这个方法知道如何去渲染每行中的每个单元格。在这个方法中,我们需要用数据对单元格进行填充。

运行程序,如果一切正常,我们将在表格中看到昆虫列表。

下载资源

为了完成本教程,你可能需要下载这些压缩包,并解压缩。

注意: 为了将昆虫分成 “一点也不可怕” 到 “极度恐怖”几个级别,你还需要用到一个开源的分级组件EDStarRating,这也被包含在压缩包中。

在本教程中,我们不会解释如何实现这个组件,而只是演示如何在项目中使用它。压缩包中还包括了一个NSImage类别,可以从一张大图片生成缩略图。 此外,还包括3张怪脸图片,分别用于显示昆虫的不同级别。

关于 EDStarRating组件,你可以参考它的 github 主页.

首先,在项目导航窗口中创建一个名为Art的文件夹,并将3个怪脸图片拖到这个文件夹中——确保“Copy items if needed” 已勾选, 以及Add to targets中的“ScaryBugsMac” 已选上。

再创建一个名为“Views” 的文件夹, 将EDStarRating.h 和EDStarRating.m拖到该文件夹。 再次确保“Copy items if needed” 已勾选以及Add to targets中的“ScaryBugsMac” 已选上。


点击Finish. 在下一窗口当被问到 “Would you like to configure an Objective-C bridgingheader?” 时选择Yes。这将创建一个Objective-C 类到Swift 代码的桥接头文件。


对于 NSImage+Extras.h 和NSImage+Extras.m,重复上述步骤,只不过这次将它们拖进的是“Helpers”文件夹。

打开ScaryBugsMac-Bridging-Header.h 加入以下import语句:

#import "EDStarRating.h" #import "NSImage+Extras.h"

 

以下为最终效果,其中桥接头文件已经被我们移到 Supporting Files 文件夹中:


创建详情页面

在iOS中,典型的“主-细页面App”需要创建两个视图,但在 OS X,由于屏幕不再受到限制,我们可以将它们合并在同一个视图中。

打开MasterViewController.xib,选中view,将宽度和高度拖大。如图:


我们需要显示下列信息: 昆虫名, 惊悚指数和昆虫图片。

昆虫名用NSTextField 控件显示,惊悚指数用EDStarRating 控件显示,昆虫图片则用NSImageView显示。

此外,我们还需要两个Label,用于表示每个字段的意义(标题)。

 

拖一个 Text Field (昆虫名), 2个Labels (字段标题), 一个Image View 到view中。

EDStarRating 控件是一个定制控件,无法在Objects Library中找到它,因此你需要先拖入一个 “Custom View”控件。

将这些控件放到view的右边,从上到下依次摆放:

·      首先是一个Label,用于充当昆虫名的字段标题,在它下边是 textfield。

·      在text field下面是第二个 label(惊悚指数的字段标题)。

·      在这个label,下边是一个customview (后面将改成EDStarRating控件)。

·      最下面是image view below 控件。

所有控件左对齐,如下图所示:


然后选中custom view 控件,打开Identity面板(第三个标签按钮)将Class 修改为EDStarRating


选择第一个label,打开Attributes 面板(第4个标签按钮),修改Title 为 “名称”.

依照上面的方法,将第二个label的title 改为“Rating”。

选择最顶级的 view (在document outline面板中显示为“Custom View”) ,打开Size 面板,查看它的大小:


打开 MainMenu.xib, 选择 ScaryBugsMac window, 设置window 的宽高为前面记住的宽高。然后勾选MinimumSize 。


运行后效果如下:


EDStarRating控件并没有在界面上显示,这是因为我们还没有配置它。

打开 MasterViewController.xib,打开Assistant Editor (工具栏中“Editor” 面板的第二个按钮), 并确保当前编辑的内容是MasterViewController.swift

选中table View,按下右键,拖一条线到MasterViewController.swift文件中:


这将弹出一个窗口,允许你创建一个IBOutlet。在Name中输入bugsTableView, Storage 设置为 Weak, 然后点击Connect。


重复上述步骤,为text field和image view创建两个IBOutlet:

bugTitleViewbugImageView

对于custom view, 则创建一个IBOutlet: bugRating.

最终, MasterViewController.swift文件中将新增如下内容:

@IBOutlet weak var bugsTableView: NSTableView!

@IBOutlet weak var bugTitleView: NSTextField!

@IBOutlet weak var bugImageView: NSImageView!

@IBOutlet weak var bugRating: EDStarRating!


显示昆虫详情

打开MasterViewController.swift 增加如下方法:

func selectedBugDoc() -> ScaryBugDoc? {

   let selectedRow = self.bugsTableView.selectedRow;

   if selectedRow >= 0 && selectedRow < self.bugs.count {

return self.bugs[selectedRow]

   }  

   return nil

}

这个方法根据用户选中的行索引,从数据模型中检索响应的对象。

然后是这个方法:

func updateDetailInfo(doc: ScaryBugDoc?) {

   var title = ""

   var image: NSImage?

   var rating = 0.0  

   if let scaryBugDoc = doc {

     title = scaryBugDoc.data.title

     image = scaryBugDoc.fullImage

     rating = scaryBugDoc.data.rating

   }  

   self.bugTitleView.stringValue = title

   self.bugImageView.image = image

   self.bugRating.rating = Float(rating)

}

这个方法根据ScaryBugDoc对象,将昆虫的信息和图片在UI上显示。然后是这个方法:

func tableViewSelectionDidChange(notification: NSNotification!) {

   let selectedDoc = selectedBugDoc()

   updateDetailInfo(selectedDoc)        

}

当用户改变了在表格中的选择时,这个方法调用前两个实用方法。

从OS X 10.10 Yosemite开始,View Controller 使用了新的

viewWillAppearviewDidLoad,以及其它iOS风格的生命周期方法。而在OS X中传统的创建视图方法一般是 loadView(), 这个方法是向后兼容的,因此我们使用这个方法:

override func loadView() {

   super.loadView()  

   self.bugRating.starImage = NSImage(named: "star.png")

   self.bugRating.starHighlightedImage = NSImage(named:"shockedface2_full.png")

   self.bugRating.starImage = NSImage(named:"shockedface2_empty.png")  

   self.bugRating.delegate = self  

   self.bugRating.maxRating = 5

   self.bugRating.horizontalMargin = 12

   self.bugRating.editable = true

   self.bugRating.displayMode = UInt(EDStarRatingDisplayFull)  

   self.bugRating.rating = Float(0.0)

}

在这里,我们初始化EDStarRating控件:用于表示昆虫惊悚指数的图片,控件的delegate属性以及其它参数。

然后在MasterViewController.swift 最后增加一个extension声明:

// MARK: - EDStarRatingProtocol  

extension MasterViewController: EDStarRatingProtocol {   }

等下在来实现这个EDStarRatingProtocol 协议。

先编译运行程序,效果如下:


添加删除

打开MasterViewController.xib ,拖两个“Gradient Button” 到 table view下。 选择其中一个按钮, 打开 Attributes 面板,删除Title属性中的内容,然后在Image属性选择,这将使按钮显示为一个“+”号。

同样,将另一个按钮设置为“-”号按钮(Image属性选择为 “NSRemoveTemplate”)。


打开Assistant Editor 窗口,确保当前内容为MasterViewController.swift文件,首先添加一个扩展的定义:

// MARK: - IBActions  

extension MasterViewController {   }

严格来说这个扩展并非必须,但通过这种方式,我们能更好地组织我们的Swift代码。然后选择加号按钮,右键拖一条线到这个扩展上。


在弹出的窗口中,Connection一栏选择Action,Name一栏输入 addBug, 然后点击Connect.


这样将创建一个 addBug(_:) 方法,每当加号按钮被点击,系统将调用这个方法。在减号按钮上重复同样步骤, Name请使用 deleteBug.

打开 MasterViewController.swift实现addBug方法如下:

// 1. 使用默认值创建一个新的ScaryBugDoc实例

let newDoc = ScaryBugDoc(title: "New Bug", rating: 0.0, thumbImage: nil, fullImage: nil)  

// 2. 将该实例添加到model 数组

self.bugs.append(newDoc)

let newRowIndex = self.bugs.count - 1  

// 3.table view插入新行

self.bugsTableView.insertRowsAtIndexes(NSIndexSet(index: newRowIndex), withAnimation: NSTableViewAnimationOptions.EffectGap)  

// 4. 选中并滚动到新行

self.bugsTableView.selectRowIndexes(NSIndexSet(index: newRowIndex), byExtendingSelection:false)

self.bugsTableView.scrollRowToVisible(newRowIndex)

实现deleteBug()方法如下:

// 1. Get selected doc

if let selectedDoc = selectedBugDoc() {

   // 2. Remove the bug from the model

   self.bugs.removeAtIndex(self.bugsTableView.selectedRow)

   // 3. Remove the selected row from the table view 

   self.bugsTableView.removeRowsAtIndexes(

NSIndexSet(index:self.bugsTableView.selectedRow),

withAnimation: NSTableViewAnimationOptions.SlideRight)  

   // 4. Clear detail info  

   updateDetailInfo(nil)

}


编辑

打开 MasterViewController.xib, 打开 Assistant Editor, 确保当前显示的文件是 MasterViewController.swift

选中text field, 右键拖到 MasterViewController.swift 文件中的addBug()方法之前:


这将允许你为Text Field创建一个IBAction,Name 请使用bugTitleDidEndEdit


这个方法将在text field结束编辑时调用(当用户按下回车键或者离开Text Field控件)。

回到MasterViewController.swift, 添加方法:

func reloadSelectedBugRow() {

   let indexSet = NSIndexSet(index: self.bugsTableView.selectedRow)

   let columnSet = NSIndexSet(index: 0)

   self.bugsTableView.reloadDataForRowIndexes(indexSet, columnIndexes:columnSet)

}

在这个方法中,我们重新加载该行数据模型,你需要在模型数据被改动后调用这个方法。

bugTitleDidEndEdit 方法实现如下:

if let selectedDoc = selectedBugDoc() {

   selectedDoc.data.title = self.bugTitleView.stringValue

   reloadSelectedBugRow()

}

首先,调用selectedBugDoc()获得相关昆虫的信息,然后从text field读取文本字符串,并用它来更新模型中的昆虫名称。最后调用reloadSelectedBugRow()通知单元格进行刷新。

注意: 通知table view自己刷新cell要比直接操纵cell的内容要好。

运行App,从列表选中某个昆虫,尝试修改其名称(记得按回车键),表格中的昆虫名将随之改变!

但是如果你切换到其他昆虫,然后返回修改的那一个昆虫,你会发现数据又回到原来(未改动前)了。这是因为我们没有将模型对象进行持久化(保存进文件)。




接下来实现EDStarRating 的编辑。 在loadView 方法中,我们已经配置了EDStarRating的delegate属性,我们仅仅需要实现相关委托方法即可。

打开MasterViewController.swift 在 EDStarRatingProtocol 扩展中添加如下方法:

func starsSelectionChanged(control: EDStarRating!, rating: Float) {

   if let selectedDoc = selectedBugDoc() {

selectedDoc.data.rating = Double(self.bugRating.rating)

   }

}

跟前面几乎一样: 获得用户选定的昆虫模型,用修改后的值赋值给它。

运行程序。需要注意的是,用户设定新的评级后这个值是被持久化的,哪怕你切换到其他昆虫然后有切换回来。


获取本地图片

打开 MasterViewController.xib,拖一个“Push Button” 控件到image view下方。

修改按钮的title 为 “Change Picture”:


如同加号按钮和减号按钮,为Change Picture 按钮创建一个IBAction,命名为 changePicture

这个action在按钮点击时调用。

OS X 有一个特有的控件叫做 IKPictureTaker,允许用户从计算机上选择一张图片,或者从摄像头捕捉一张图片。

当用户选择了图片之后,这个控件会调用指定的delegate方法。

打开MasterViewController.swift 加入以下import 语句:

import Quartz

这个 image picker属于 Quartz 框架。

changePicture方法中,添加代码:

if let selectedDoc = selectedBugDoc() {

   IKPictureTaker().beginPictureTakerSheetForWindow(self.view.window,

withDelegate: self,

     didEndSelector: "pictureTakerDidEnd:returnCode:contextInfo:",

     contextInfo: nil)

}

我们先检查用户是否选择了有效的昆虫,如果是,显示picture taker控件。

然后实现pictureTakerDidEnd(_:returnCode:contextInfo:)方法:

func pictureTakerDidEnd(picker: IKPictureTaker, returnCode: NSInteger, contextInfo: UnsafePointer<Void>) {

   let image = picker.outputImage()

   if image != nil && returnCode == NSOKButton {

self.bugImageView.image = image

if let selectedDoc = selectedBugDoc() {

       selectedDoc.fullImage = image

       selectedDoc.thumbImage =image.imageByScalingAndCroppingForSize(CGSize(width: 44, height: 44))       reloadSelectedBugRow()

}  

   }

}

首先检查用户是否点击了OK (NSOKButton) 以及选择的图片是否有效。

如果是,获取用户选定的昆虫模型,修改昆虫的图片及缩略图,然后更新cell。

运行程序,选择一个昆虫,点击Change Picture, 从本地文件或摄像头中获取一张图片,这张图片将立即在选定的cell中得到更新。

一些细节上的问题

当你运行程序,视图改变窗口大小,你会发现控件并不能自动适应大小。


这是窗口拖大后的效果。


pplns:o="urn:schemas-microsoft-com:office:office"xmlns:w="urn:schemas-microsoft-com:office:word"xmlns:m="http://schemas.microsoft.com/office/2004/12/omml"xmlns="http://www.w3.org/TR/REC-html40">

这是窗口缩小后的效果。

另外,我们还没有为App数据进行持久化。一旦App重启,用户对数据进行的增加和修改都会丢失。

打开MasterViewController.xib,将View的Size缩小至最小能够足以显示所有控件的程度。


在上图中,3个按钮放在了同一排。在右边细节展示区域中,所有的控件都左对齐,且宽度一致(除了ChangePicture按钮)。

然后,我们在中间增加一个分割线。拖一个 VerticalLine 到View的中央。


复原操作

复原操作用于将数据恢复至原来的状态。拖一个Push 按钮在Table View下方,修改其标题为Reset。然后打开Assistant Editor,为按钮创建一个IBAction,名为resetData(确认当前打开的源文件为MasterViewController.swift )。


resetData()方法加入如下代码:

setupSampleBugs() 
updateDetailInfo(nil) 
bugsTableView.reloadData()

setupSampleBugs() 方法调用会恢复所有模型数据。 以nil作为参数值调用updateDetailInfo 方法将清除所有细节字段。然后刷新Table View。

运行程序,添加、删除或修改任意数据。然后点击Reset按钮,所有数据又恢复原样。




缩放

打开MasterViewController.xib,在Size面板中查看 Custome View的大小。在本例中,它应该是540x400大小。但是读者的这个数字会有不同。不管是多大,请记下这个数字。待会会用到。


这将是App出口的最小大小。打开 MainMenu.xib, 选择 window 对象。在Size 面板中,勾上Constraint右边的Minimum Size 选项,然后将width 和 height 修改为同样的值。


运行程序。


改变出口的大小,这次当窗口缩小到最小尺寸后,就无法再缩小。

接下来我们需要解决控件自适应大小的问题,包括TableView和细节页面中的控件。

首先是MasterViewController视图。

打开 AppDelegate.swift, 在applicationDidFinishLaunching()方法最后加入:

// 3. 设置 masterViewController.view的布局约束 masterViewController.view.translatesAutoresizingMaskIntoConstraints = false 
let verticalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("V:|[subView]|",
   options: NSLayoutFormatOptions(0),
   metrics: nil,
   views: ["subView" : masterViewController.view]) 
let horizontalConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[subView]|",
   options: NSLayoutFormatOptions(0),
   metrics: nil,
   views: ["subView" : masterViewController.view])   
NSLayoutConstraint.activateConstraints(verticalConstraints + horizontalConstraints)

在这里,我们允许MasterViewController在宽、高两个维度上使用自动布局。

接下来,我们使用IB的自动布局来约束来对table view进行布局,以便在窗口大小改变时,让它的高度自动增长,但宽度保持恒定。

打开 MasterViewController.xib ,选择table view,点击右下角的Pin 按钮, 勾上上、左、下3个约束,以及一个等宽约束,然后点击 “Add 4 Constraints”:


注意图片中的约束值可能和读者的实际值有所不同。

然后选择Reset按钮,设置其与Table View的上边距约束和一个相对于Main View的左边距约束:


接着选择分隔线,设置其与Main View的上、下边距约束,以及与TableView的左边距约束,确认在左边距约束的下拉列表中选择了 “Bordered ScrollView – Table View” :


接下来设置“Add” 和 “Delete” 按钮。我们不需要改变它们的大小,唯一需要改变的是它们和Table View之间的距离。对于两个按钮,我们需要设置它们的左、上,宽度和高度约束。已Add按钮为例,显示如下图:


Delete按钮类似。

运行程序,改变窗口大小,查看效果。


然后是右边的细节页面。在这个页面中,当窗口宽度变大时,所有控件的宽度也会变大。以TableView相同的方法,分别设置它们的自动布局如下:

·      设置Name标签的左、上约束。

·      设置bugTitleView的左、上约束。

·      设置Rating标签的左、上约束。

·      设置 bugRating的左、上、右和高约束。

·      设置bugImageView的左、上、下、右约束。

·      移动 Change Picture 按钮,以便它的右边沿刚好和bugImageView的右边沿平齐,然后设置它的右、下约束。

 

设置完bugImageView 按钮可能会出现几个警告,但ChangePicture 按钮之后,这些警告会消除。

上述步骤做完后,故事板将如下图所示:


编译运行,再次缩放窗口。


bugImageView的Scale设置会对图片产生不同的显示效果。在IB中选择Image Well 控件, 修改其scaling属性为“Proportionally Up or Down” 或者 “Axes Independently”,然后运行App,看看有什么不同。

注意: 如果你想限制窗口的最大缩放尺寸,则你也可以用设置窗口最小缩放尺寸同样的方式加以限制。



关注细节

关于用户体验方面,我们仍然有一些细节值得注意。例如:运行App,不要选择任何昆虫,点击“Delete” 或者 “Change Picture” 按钮,什么都不会发生,Why?

作为程序员,你当然知道当用户什么都没选择的情况下,不应当执行任何操作,但对于用户而言,这种情况仍然显得不太友好:


我们通过以下方式来解决这个问题:

·      如果用户选中了某个单元格,我们才让Delete按钮、Change picture按钮、文本框和rating view可用。

·      如果用户未选择任何行,我们禁用上述控件,用户将不能和它们进行任何交互。

打开MasterViewController.xib,选择Delete按钮,在属性面板,将Enabled属性前的勾去掉。


在ChangePicture 按钮、text field上重复上述步骤。

这样,当程序刚启动时,上述控件将被禁用。然后我们需要在用户选择了表格中的单元格之后再启用它们。要实现这个目的,我们首先需要为它们创建IBOutlet。

打开AssistantEditor 确保当前编辑的文件为MasterViewController.swift

选择“Delete” 按钮,右键将它拖动到 MasterViewController.swift文件中。


在弹出的出口中,选择connection为“Outlet”, name 栏输入 deleteButton,然后点击Connect.


重复上述步骤,为Changepicture按钮创建一个IBOutlet,名为changePictureButton.

打开MasterViewController.swift, 在tableViewSelectionDidChange(_:),加入以下代码,位于 updateDetailInfo(selectedDoc)一行以后:

// Enable/disable buttons based on the selection 
let buttonsEnabled = (selectedDoc != nil) 
deleteButton.enabled = buttonsEnabled 
changePictureButton.enabled = buttonsEnabled 
bugRating.editable = buttonsEnabled 
bugTitleView.enabled = buttonsEnabled

我们首先判断控件是否需要被启用,这是通过用户是否选中单元格来决定的。如果selectedDoc为空,则意味着没有行被选中,这说明控件应当被禁用,否则启用控件。

此外,ratingview 默认是启用的,所以我们还需要在

 loadView() 中禁用它。找到这行语句:

self.bugRating.editable = true

修改为

self.bugRating.editable = false

运行程序。

注意: 你还可以在用户未选择有效行时讲整个细节页面都隐藏起来,但这完全取决于你。

保存数据

就像iOS,Mac App也能够使用 NSUserDefaults, 因此我们完全可以把数据存放到那里。

首先我们必须让模型类实现NSCoding 协议。在ScaryBugData.swift 中定义一个扩展:

// MARK: - NSCoding   
extension ScaryBugData: NSCoding {
   func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.title, forKey: "title")
coder.encodeObject(Double(self.rating), forKey: "rating")
   } 
}

首先我们让ScaryBugData实现NSCoding协议中的 encodeWithCoder方法。这个方法用于对自定义类进行编码。

同时还需要一个与之对应的初始化方法。不同的是,我们无法在扩展中定义required的init方法,因此必须把它定义在类代码中:

required convenience init(coder decoder: NSCoder) {
   self.init() 
   self.title = decoder.decodeObjectForKey("title") as String
   self.rating = decoder.decodeObjectForKey("rating") as Double 
} 

init(coder:) 方法和encodeWithCoder方法向反,用于从文件中读取数据并反编码为对象。

然后在ScaryBugDoc.swift 中定义一个扩展实现 NSCoding 协议:

// MARK: - NSCoding 
extension ScaryBugDoc: NSCoding {
   func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.data, forKey: "data")
coder.encodeObject(self.thumbImage, forKey: "thumbImage") 
coder.encodeObject(self.fullImage, forKey: "fullImage")
} 
}

然后在类代码中(不要在扩展定义中)定义Init方法:

required convenience init(coder decoder: NSCoder) {
   self.init()
   self.data = decoder.decodeObjectForKey("data") as ScaryBugData
   self.thumbImage = decoder.decodeObjectForKey("thumbImage") as NSImage?
   self.fullImage = decoder.decodeObjectForKey("fullImage") as NSImage? 
}

接下来将模型数据保存到NSUserDefaults. 在 MasterViewController.swift中添加一个方法:

func saveBugs() {
   let data = NSKeyedArchiver.archivedDataWithRootObject(self.bugs)
   NSUserDefaults.standardUserDefaults().setObject(data, forKey: "bugs")
   NSUserDefaults.standardUserDefaults().synchronize() 
}
这个方法首先将bugs数组构建为一个NSData对象,然后保存到

NSUserDefaults.NSKeyedArchiver。当然数组中的所有对象都实现了 NSCoding.

打开 AppDelegate.swift, 在applicationWillTerminate()中加入:

masterViewController.saveBugs()

这样,在App退出之前,将所有昆虫数据保存到了 NSUserDefaults.

加载数据

AppDelegate.swift, 找到applicationDidFinishLaunching 的

masterViewController.setupSampleBugs()

替换为

if let data = NSUserDefaults.standardUserDefaults().objectForKey("bugs") as? NSData {
   masterViewController.bugs = NSKeyedUnarchiver.unarchiveObjectWithData(data) as [ScaryBugDoc] 
} else {
   masterViewController.setupSampleBugs() 
}

运行程序,添加、删除和编辑昆虫数据,然后退出程序。重新启动App之后,所有上次进行的修改都被保留住了。

注意: 如果应用程序不是正常的退出,则saveBugs() 方法不会调用 — 请用Command-Q 退出程序,而不是从Xcode中终止程序。要解决这个问题,你可以在MasterViewController的某个恰当的时机调用saveBug()方法——只要用户进行过新建、删除和编辑操作。

 





2016-07-28 12:11:46 yanghuiliu 阅读数 12017

猴子原创,欢迎转载。转载请注明: 转载自Cocos2Der-CSDN,谢谢!
原文地址: http://blog.csdn.net/cocos2der/article/details/52054107

这两天突然想看看OSX下的App开发,看了几篇文章。下面这一篇我觉得入门是非常好的。我仅转述为中文,并非原文翻译。原文地址:http://footle.org/WeatherBar/

下面开始介绍如何使用Swift开发一个Mac Menu Bar(Status Bar) App。通过做一个简单的天气app。天气数据来源于OpenWeatherMap

完成后的效果如下:
这里写图片描述

一、开始建立工程

打开Xcode,Create a New Project or File ⟶ New ⟶ Project ⟶ Application ⟶ Cocoa Application ( OS X 这一栏)。点击下一步。
这里写图片描述

二、开始代码工作

  1. 打开MainMenu.xib,删除默认的windows和menu菜单。因为我们是状态栏app,不需要菜单栏,不需要主窗口。
    这里写图片描述

  2. 添加一个Menu菜单
    这里写图片描述
    删除其中默认的2个子菜单选项,仅保留1个。并将保留的这个改名为“Quit”。

  3. 打开双视图绑定Outlet

    • 将Menu Outlet到AppDelegate,命名为statusMenu
      这里写图片描述

    • 将子菜单Quit绑定Action到AppDelegate,命名为quitClicked
      这里写图片描述

    • 你可以删除 @IBOutlet weak var window: NSWindow! ,这个app中用不上。

  4. 代码

    • 在AppDelegate.swift中statusMenu下方添加

      let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
    • applicationDidFinishLaunching函数中添加:

      statusItem.title = "WeatherBar"
      statusItem.menu = statusMenu
    • 在quitClicked中添加:

      NSApplication.sharedApplication().terminate(self)
    • 此时你的代码应该如下:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    @IBOutlet weak var statusMenu: NSMenu!

    let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)

    @IBAction func quitClicked(sender: NSMenuItem) {
        NSApplication.sharedApplication().terminate(self)
    }

    func applicationDidFinishLaunching(aNotification: NSNotification) {
        statusItem.title = "WeatherBar"
        statusItem.menu = statusMenu
    }

    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }

}

运行,你可以看到一个状态栏了。

三、进阶一步,让App变得更好

你应该注意到了,当你运行后,底部Dock栏里出现了一个App启动的Icon。但实际上我们也不需要这个启动icon,打开Info,添加 “Application is agent (UIElement)”为YES。
这里写图片描述

运行一下,不会出现dock启动icon了。

四、添加状态栏Icon

状态栏icon尺寸请使用18x18这里写图片描述, 36x36(@2x)这里写图片描述, 54x54(@3x),添加这1x和2x两张图到Assets.xcassets中。
这里写图片描述

在applicationDidFinishLaunching中,修改为如下:

let icon = NSImage(named: "statusIcon")
icon?.template = true // best for dark mode
statusItem.image = icon
statusItem.menu = statusMenu

运行一下,你应该看到状态栏icon了。

五、重构下代码

如果我们进一步写下去,你会发现大量代码在AppDelegate中,我们不希望这样。下面我们为Menu创建一个Controller来管理。

  • 新建一个NSObject的StatusMenuController.swift, File ⟶ New File ⟶ OS X Source ⟶ Cocoa Class ⟶ Next
    这里写图片描述

代码如下:

// StatusMenuController.swift

import Cocoa

class StatusMenuController: NSObject {
    @IBOutlet weak var statusMenu: NSMenu!

    let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)

    override func awakeFromNib() {
        let icon = NSImage(named: "statusIcon")
        icon?.template = true // best for dark mode
        statusItem.image = icon
        statusItem.menu = statusMenu
    }

    @IBAction func quitClicked(sender: NSMenuItem) {
        NSApplication.sharedApplication().terminate(self)
    }
}
  • 还原AppDelegate,修改为如下:
// AppDelegate.swift

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    func applicationDidFinishLaunching(aNotification: NSNotification) {
        // Insert code here to initialize your application
    }
    func applicationWillTerminate(aNotification: NSNotification) {
        // Insert code here to tear down your application
    }
}

注意,因为删除了AppDelegate中的Outlet注册,所以你需要重新连Outlet,但在这之前我们需要先做一件事。(你可以试试连接StatusMenuController中的Outlet,看看会怎么样?)

  • 打开MainMenu.xib,添加一个Object。
    这里写图片描述
    将该Object的Class指定为StatusMenuController
    这里写图片描述
    重建Outlet到StatusMenuController,注意删除之前连接到AppDelegate的Outlet
    这里写图片描述

当MainMenu.xib被初始化的时候,StatusMenuController下的awakeFromNib将会被执行,所以我们在里面做初始化工作。

运行一下,保证你全部正常工作了。

六、天气Api

我们使用 OpenWeatherMap的天气数据,所以你得注册一个账号,获取到免费的API Key。

  • 添加WeatherAPI.swift, File ⟶ New File ⟶ OS X Source ⟶ Swift File ⟶ WeatherAPI.swift,加入如下代码,并使用你自己的API Key。
import Foundation

class WeatherAPI {
    let API_KEY = "your-api-key-here"
    let BASE_URL = "http://api.openweathermap.org/data/2.5/weather"

    func fetchWeather(query: String) {
        let session = NSURLSession.sharedSession()
        // url-escape the query string we're passed
        let escapedQuery = query.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())
        let url = NSURL(string: "\(BASE_URL)?APPID=\(API_KEY)&units=imperial&q=\(escapedQuery!)")
        let task = session.dataTaskWithURL(url!) { data, response, err in
            // first check for a hard error
            if let error = err {
                NSLog("weather api error: \(error)")
            }

            // then check the response code
            if let httpResponse = response as? NSHTTPURLResponse {
                switch httpResponse.statusCode {
                case 200: // all good!
                    let dataString = NSString(data: data!, encoding: NSUTF8StringEncoding) as! String
                    NSLog(dataString)
                case 401: // unauthorized
                    NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
                default:
                    NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
                }
            }
        }
        task.resume()
    }
}
  • 添加一个Update子菜单到Status Menu。
    这里写图片描述
    绑定Action到StatusMenuController.swift,取名为updateClicked

  • 开始使用WeatherAPI, 在StatusMenuController中let statusItem下面加入:
    let weatherAPI = WeatherAPI(),
    在updateClicked中加入:
    weatherAPI.fetchWeather("Seattle")

注意OSX 10.11之后请添加NSAppTransportSecurity,保证http能使用。

运行一下,然后点击Update菜单。你会收到一个json格式的天气数据。

  • 我们再调整下StatusMenuController代码, 添加一个updateWeather函数,修改后如下:
import Cocoa

class StatusMenuController: NSObject {
    @IBOutlet weak var statusMenu: NSMenu!

    let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(NSVariableStatusItemLength)
    let weatherAPI = WeatherAPI()

    override func awakeFromNib() {
        statusItem.menu = statusMenu
        let icon = NSImage(named: "statusIcon")
        icon?.template = true // best for dark mode
        statusItem.image = icon
        statusItem.menu = statusMenu

        updateWeather()
    }

    func updateWeather() {
        weatherAPI.fetchWeather("Seattle")
    }

    @IBAction func updateClicked(sender: NSMenuItem) {
        updateWeather()
    }

    @IBAction func quitClicked(sender: NSMenuItem) {
        NSApplication.sharedApplication().terminate(self)
    }
}

七、解析Json

你可以使用 SwiftyJSON,但本次我们先不使用第三方库。我们得到的天气数据如下:

{
    "coord": {
        "lon": -122.33,
        "lat": 47.61
    },
    "weather": [{
        "id": 800,
        "main": "Clear",
        "description": "sky is clear",
        "icon": "01n"
    }],
    "base": "cmc stations",
    "main": {
        "temp": 57.45,
        "pressure": 1018,
        "humidity": 59,
        "temp_min": 53.6,
        "temp_max": 62.6
    },
    "wind": {
        "speed": 2.61,
        "deg": 19.5018
    },
    "clouds": {
        "all": 1
    },
    "dt": 1444623405,
    "sys": {
        "type": 1,
        "id": 2949,
        "message": 0.0065,
        "country": "US",
        "sunrise": 1444659833,
        "sunset": 1444699609
    },
    "id": 5809844,
    "name": "Seattle",
    "cod": 200
}
  • 在WeatherAPI.swift添加天气结构体用于解析son
struct Weather {
    var city: String
    var currentTemp: Float
    var conditions: String
}
  • 解析son
 func weatherFromJSONData(data: NSData) -> Weather? {
        typealias JSONDict = [String:AnyObject]
        let json : JSONDict

        do {
            json = try NSJSONSerialization.JSONObjectWithData(data, options: []) as! JSONDict
        } catch {
            NSLog("JSON parsing failed: \(error)")
            return nil
        }

        var mainDict = json["main"] as! JSONDict
        var weatherList = json["weather"] as! [JSONDict]
        var weatherDict = weatherList[0]

        let weather = Weather(
            city: json["name"] as! String,
            currentTemp: mainDict["temp"] as! Float,
            conditions: weatherDict["main"] as! String
        )

        return weather
    }
  • 修改fetchWeather函数去调用weatherFromJSONData
let task = session.dataTaskWithURL(url!) { data, response, error in
        // first check for a hard error
    if let error = err {
        NSLog("weather api error: \(error)")
    }

    // then check the response code
    if let httpResponse = response as? NSHTTPURLResponse {
        switch httpResponse.statusCode {
        case 200: // all good!
            if let weather = self.weatherFromJSONData(data!) {
                NSLog("\(weather)")
            }
        case 401: // unauthorized
            NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
        default:
            NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
        }
    }
}

如果此时你运行,你会收到

2016-07-28 11:25:08.457 WeatherBar[49688:1998824] Optional(WeatherBar.Weather(city: "Seattle", currentTemp: 51.6, conditions: "Clouds"))
  • 给Weather结构体添加一个description
struct Weather: CustomStringConvertible {
    var city: String
    var currentTemp: Float
    var conditions: String

    var description: String {
        return "\(city): \(currentTemp)F and \(conditions)"
    }
}

再运行试试。

八、Weather用到Controller中

  • 在 WeatherAPI.swift中增加delegate协议
protocol WeatherAPIDelegate {
    func weatherDidUpdate(weather: Weather)
}
  • 声明var delegate: WeatherAPIDelegate?

  • 添加初始化

init(delegate: WeatherAPIDelegate) {
    self.delegate = delegate
}
  • 修改fetchWeather
let task = session.dataTaskWithURL(url!) { data, response, error in
    // first check for a hard error
    if let error = err {
        NSLog("weather api error: \(error)")
    }

    // then check the response code
    if let httpResponse = response as? NSHTTPURLResponse {
        switch httpResponse.statusCode {
        case 200: // all good!
            if let weather = self.weatherFromJSONData(data!) {
                self.delegate?.weatherDidUpdate(weather)
            }
        case 401: // unauthorized
            NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
        default:
            NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
        }
    }
}
  • StatusMenuController添加WeatherAPIDelegate
class StatusMenuController: NSObject, WeatherAPIDelegate {
...
  var weatherAPI: WeatherAPI!

  override func awakeFromNib() {
    ...
    weatherAPI = WeatherAPI(delegate: self)
    updateWeather()
  }
  ...
  func weatherDidUpdate(weather: Weather) {
    NSLog(weather.description)
  }
  ...
  • Callback实现,修改WeatherAPI.swift中fetchWeather:
    func fetchWeather(query: String, success: (Weather) -> Void) {
    修改fetchWeather内容
let task = session.dataTaskWithURL(url!) { data, response, error in
    // first check for a hard error
    if let error = err {
        NSLog("weather api error: \(error)")
    }

    // then check the response code
    if let httpResponse = response as? NSHTTPURLResponse {
        switch httpResponse.statusCode {
        case 200: // all good!
            if let weather = self.weatherFromJSONData(data!) {
                success(weather)
            }
        case 401: // unauthorized
            NSLog("weather api returned an 'unauthorized' response. Did you set your API key?")
        default:
            NSLog("weather api returned response: %d %@", httpResponse.statusCode, NSHTTPURLResponse.localizedStringForStatusCode(httpResponse.statusCode))
        }
    }
}
  • 在controller中
func updateWeather() {
    weatherAPI.fetchWeather("Seattle, WA") { weather in
        NSLog(weather.description)
    }
}

运行一下,确保都正常。

九、显示天气

在MainMenu.xib中添加子菜单 “Weather”(你可以添加2个Separator Menu Item用于子菜单分割线)
这里写图片描述

在updateWeather中,替换NSLog:

if let weatherMenuItem = self.statusMenu.itemWithTitle("Weather") {
    weatherMenuItem.title = weather.description
}

运行一下,看看天气是不是显示出来了。

十、创建一个天气视图

打开MainMenu.xib,拖一个Custom View进来。

  • 拖一个Image View到Custom View中,设置ImageView宽高度为50。
    这里写图片描述

  • 拖两个Label进来,分别为City和Temperature
    这里写图片描述

  • 创建一个名为WeatherView的NSView,New File ⟶ OS X Source ⟶ Cocoa Class
    在MainMenu.xib中,将Custom View的Class指定为WeatherView
    这里写图片描述

  • 绑定WeatherView Outlet:

import Cocoa

class WeatherView: NSView {
    @IBOutlet weak var imageView: NSImageView!
    @IBOutlet weak var cityTextField: NSTextField!
    @IBOutlet weak var currentConditionsTextField: NSTextField!
}

并添加update:

func update(weather: Weather) {
    // do UI updates on the main thread
    dispatch_async(dispatch_get_main_queue()) {
        self.cityTextField.stringValue = weather.city
        self.currentConditionsTextField.stringValue = "\(Int(weather.currentTemp))°F and \(weather.conditions)"
        self.imageView.image = NSImage(named: weather.icon)
    }
}

注意这里使用dispatch_async调用UI线程来刷新UI,因为后面调用此函数的数据来源于网络请求子线程。

  • StatusMenuController添加weatherView outlet
class StatusMenuController: NSObject {
    @IBOutlet weak var statusMenu: NSMenu!
    @IBOutlet weak var weatherView: WeatherView!
    var weatherMenuItem: NSMenuItem!
    ...
  • 子菜单Weather绑定到视图
weatherMenuItem = statusMenu.itemWithTitle("Weather")
weatherMenuItem.view = weatherView
  • update中:
func updateWeather() {
    weatherAPI.fetchWeather("Seattle, WA") { weather in
        self.weatherView.update(weather)
    }
}

运行一下。

十一、添加天气图片

先添加天气素材到Xcode,天气素材可以在http://openweathermap.org/weather-conditions 这里找到。这里我已经提供了一份icon zip, 解压后放Xcode。
这里写图片描述

  • WeatherAPI.swift的Weather struct中,添加 var icon: String

  • 在weatherFromJSONData中:

let weather = Weather(
    city: json["name"] as! String,
    currentTemp: mainDict["temp"] as! Float,
    conditions: weatherDict["main"] as! String,
    icon: weatherDict["icon"] as! String
)
  • 在weatherFromJSONData:
let weather = Weather(
    city: json["name"] as! String,
    currentTemp: mainDict["temp"] as! Float,
    conditions: weatherDict["main"] as! String,
    icon: weatherDict["icon"] as! String
)
  • 在WeatherView的update中:
imageView.image = NSImage(named: weather.icon)

运行一下,Pretty!

这里写图片描述

十二、添加设置

在MainMenu.xib MenuItem中,添加一个Menu Item命名为“Preferences…”
并绑定action,命名为“preferencesClicked”

  • 添加NSWindowController命名为PreferencesWindow.swift New ⟶ File ⟶ OS X Source ⟶ Cocoa Class , 勾选同时创建XIB.在XIB中添加Label和Text Field。效果如下:
    这里写图片描述

Outlet cityTextField到PreferencesWindow.swift

  • 在PreferencesWindow.swift中添加:
override var windowNibName : String! {
    return "PreferencesWindow"
}
  • windowDidLoad()中修改:
self.window?.center()
self.window?.makeKeyAndOrderFront(nil)
NSApp.activateIgnoringOtherApps(true)
  • 最终PreferencesWindow.swift如下:
import Cocoa

class PreferencesWindow: NSWindowController {
    @IBOutlet weak var cityTextField: NSTextField!

    override var windowNibName : String! {
        return "PreferencesWindow"
    }

    override func windowDidLoad() {
        super.windowDidLoad()

        self.window?.center()
        self.window?.makeKeyAndOrderFront(nil)
        NSApp.activateIgnoringOtherApps(true)
    }
}
  • StatusMenuController.swift中添加preferencesWindow
    var preferencesWindow: PreferencesWindow!

  • awakeFromNib中,注意在updateWeather()之前:
    preferencesWindow = PreferencesWindow()

  • preferencesClicked中:
    preferencesWindow.showWindow(nil)

  • 下面为 preferences window 添加NSWindowDelegate,刷新视图。
    class PreferencesWindow: NSWindowController, NSWindowDelegate {
    并增加

func windowWillClose(notification: NSNotification) {
    let defaults = NSUserDefaults.standardUserDefaults()
    defaults.setValue(cityTextField.stringValue, forKey: "city")
}

增加协议:

protocol PreferencesWindowDelegate {
    func preferencesDidUpdate()
}

增加delegate:

var delegate: PreferencesWindowDelegate?

在windowWillClose最下面调用

delegate?.preferencesDidUpdate()
  • 回到StatusMenuController中,添加PreferencesWindowDelegate
class StatusMenuController: NSObject, PreferencesWindowDelegate {

实现代理:

func preferencesDidUpdate() {
    updateWeather()
}

awakeFromNib中:

preferencesWindow = PreferencesWindow()
preferencesWindow.delegate = self
  • 在StatusMenuController中增加默认城市
    let DEFAULT_CITY = “Seattle, WA”

  • 修改updateWeather

func updateWeather() {
    let defaults = NSUserDefaults.standardUserDefaults()
    let city = defaults.stringForKey("city") ?? DEFAULT_CITY
    weatherAPI.fetchWeather(city) { weather in
        self.weatherView.update(weather)
    }
}
  • 咱们也可以在PreferencesWindow.swift windowDidLoad中设置city默认值
let defaults = NSUserDefaults.standardUserDefaults()
let city = defaults.stringForKey("city") ?? DEFAULT_CITY
cityTextField.stringValue = city

运行。一切OK。

其他:
- 你也可以试试使用NSRunLoop.mainRunLoop().addTimer(refreshTimer!, forMode: NSRunLoopCommonModes) 来定时updateWeather.
- 试试点击天气后跳转到天气中心 NSWorkspace.sharedWorkspace().openURL(url: NSURL))
- 完整工程: WeatherBar

2018-10-30 11:15:19 IT_liuchengli 阅读数 4117

      本博文适合有iOS APP 开发经验,并且会用swift的开发人员。

       环境为 xcode10.1  swift4.2

 

     首先,Mac桌面级程序开发用的开发语言开发工具和iOS开发都是一样,都是swift(或者Objective-C)和Xcode。

     同时,很多在iOS上用的第三方库(网络请求,屏幕适配等等)绝大多数都支持Mac OX开发桌面程序。所以给与了有iOS开发经验的人很大的便利,比较轻松的转到Mac桌面开发。当然,iOS和Mac OX 开发主要在系统的视图组件和一些系统API不太一样。

本文分二部分:

第一部分为相关的学习参考资料网址

第二部分为工程的一个创建

 

 

一.列举学习参考的网站

macOSk开发入门笔记

https://blog.csdn.net/lovechris00/article/details/72779076

 

二.工程基本创建

1,用Xcode 创建Mac OS app 开发,如下图: 

 

 

2,Mac OS 开发的项目结构和iOS的非常类似,如下图

 

 

 

 

 

3.Appdegeate 及controller

 

2014-06-05 17:09:20 jwdstef 阅读数 7487


翻译自苹果的官方文档:The Swift Programming Language

简介

今天凌晨Apple刚刚发布了Swift编程语言,本文从其发布的书籍《The Swift Programming Language》中摘录和提取而成。希望对各位的iOS&OSX开发有所帮助。

Swift是供iOS和OS X应用编程的新编程语言,基于C和Objective-C,而却没有C的一些兼容约束。Swift采用了安全的编程模式和添加现代的功能来是的编程更加简单、灵活和有趣。界面则基于广受人民群众爱戴的Cocoa和Cocoa Touch框架,展示了软件开发的新方向。

Swift已经存在了多年。Apple基于已有的编译器、调试器、框架作为其基础架构。通过ARC(Automatic Reference Counting,自动引用计数)来简化内存管理。我们的框架栈则一直基于Cocoa。Objective-C进化支持了块、collection literal和模块,允许现代语言的框架无需深入即可使用。(by gashero)感谢这些基础工作,才使得可以在Apple软件开发中引入新的编程语言。

Objective-C开发者会感到Swift的似曾相识。Swift采用了Objective-C的命名参数和动态对象模型。提供了对Cocoa框架和mix-and-match的互操作性。基于这些基础,Swift引入了很多新功能和结合面向过程和面向对象的功能。

Swift对新的程序员也是友好的。他是工业级品质的系统编程语言,却又像脚本语言一样的友好。他支持playground,允许程序员实验一段Swift代码功能并立即看到结果,而无需麻烦的构建和运行一个应用。

Swift集成了现代编程语言思想,以及Apple工程文化的智慧。编译器是按照性能优化的,而语言是为开发优化的,无需互相折中。(by gashero)可以从"Hello, world"开始学起并过渡到整个系统。所有这些使得Swift成为Apple软件开发者创新的源泉。

Swift是编写iOS和OSX应用的梦幻方式,并且会持续推进新功能的引入。我们迫不及待的看到你用他来做点什么。

Swift 初见

通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”。在 Swift 中,可以用一行代码实现: 
  1. println("hello, world"
 
如果你写过 C 或者 Objective-C 代码,那你应该很熟悉这种形式——在 Swift 中,这行代码就是一个完整的程序。你不需要为了输入输出或者字符串处理导入一个单独的库。全局作用域中的代码会被自动当做程序的入口点,所以你也不需要main函数。你同样不需要在每个语句结尾写上分号。
 
这个教程会通过一系列编程例子来让你对 Swift 有初步了解,如果你有什么不理解的地方也不用担心——任何本章介绍的内容都会在后面的章节中详细讲解。
 
 注意:为了获得最好的体验,在 Xcode 当中使用代码预览功能。代码预览功能可以让你编辑代码并实时看到运行结果。
 

简单值

使用let来声明常量,使用var来声明变量。一个常量的值在编译时并不需要获取,但是你只能为它赋值一次。也就是说你可以用常量来表示这样一个值:你只需要决定一次,但是需要使用很多次。
  1. var myVariable = 42 
  2. myVariable = 50 
  3. let myConstant = 42 
  
常量或者变量的类型必须和你赋给它们的值一样。然而,声明时类型是可选的,声明的同时赋值的话,编译器会自动推断类型。在上面的例子中,编译器推断出myVariable是一个整数(integer)因为它的初始值是整数。
 
如果初始值没有提供足够的信息(或者没有初始值),那你需要在变量后面声明类型,用冒号分割。
  1. let implicitInteger = 70 
  2. let implicitDouble = 70.0 
  3. let explicitDouble: Double = 70 
 
练习:创建一个常量,显式指定类型为Float并指定初始值为4。
 
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
  1. let label = "The width is" 
  2. let width = 94 
  3. let widthLabel = label + String(width) 
  
练习:删除最后一行中的String,错误提示是什么?
 
有一种更简单的把值转换成字符串的方法:把值写到括号中,并且在括号之前写一个反斜杠。例如:
  1. let apples = 3 
  2. let oranges = 5 
  3. let appleSummary = "I have \(apples) apples." 
  4. let fruitSummary = "I have \(apples + oranges) pieces of fruit." 
 
练习:使用\()来把一个浮点计算转换成字符串,并加上某人的名字,和他打个招呼。
 
使用方括号[]来创建数组和字典,并使用下标或者键(key)来访问元素。
  1. var shoppingList = ["catfish""water""tulips""blue paint"
  2. shoppingList[1] = "bottle of water" 
  3.   
  4. var occupations = [ 
  5.     "Malcolm""Captain"
  6.     "Kaylee""Mechanic"
  7. occupations["Jayne"] = "Public Relations" 
  
要创建一个空数组或者字典,使用初始化语法。 
  1. let emptyArray = String[]() 
  2. let emptyDictionary = Dictionary<String, Float>() 
 
如果类型信息可以被推断出来,你可以用[]和[:]来创建空数组和空字典——就像你声明变量或者给函数传参数的时候一样。
  1. shoppingList = []   // 去逛街并买点东西 
 

控制流

使用if和switch来进行条件操作,使用for-in、for、while和do-while来进行循环。包裹条件和循环变量括号可以省略,但是语句体的大括号是必须的。 
  1. let individualScores = [75, 43, 103, 87, 12] 
  2. var teamScore = 0 
  3. for score in individualScores { 
  4.     if score > 50 { 
  5.         teamScore += 3 
  6.     } else { 
  7.         teamScore += 1 
  8.     } 
  9. teamScore 
 
在if语句中,条件必须是一个布尔表达式——像if score { ... }这样的代码是错误的。
 
你可以一起使用if和let来处理值缺失的情况。有些变量的值是可选的。一个可选的值可能是一个具体的值或者是nil,表示值缺失。在类型后面加一个问号来标记这个变量的值是可选的。
  1. var optionalString: String? = "Hello" 
  2. optionalString == nil 
  3.   
  4. var optionalName: String? = "John Appleseed" 
  5. var greeting = "Hello!" 
  6. if let name = optionalName { 
  7.     greeting = "Hello, \(name)" 
  
练习:把optionalName改成nil,greeting会是什么?添加一个else语句,当optionalName是nil时给greeting赋一个不同的值。
 
如果变量的可选值是nil,条件会判断为false,大括号中的代码会被跳过。如果不是nil,会将值赋给let后面的常量,这样代码块中就可以使用这个值了。
 
switch支持任意类型的数据以及各种比较操作——不仅仅是整数以及测试相等。
  1. let vegetable = "red pepper" 
  2. switch vegetable { 
  3. case "celery"
  4.     let vegetableComment = "Add some raisins and make ants on a log." 
  5. case "cucumber""watercress"
  6.     let vegetableComment = "That would make a good tea sandwich." 
  7. case let x where x.hasSuffix("pepper"): 
  8.     let vegetableComment = "Is it a spicy \(x)?" 
  9. default
  10.     let vegetableComment = "Everything tastes good in soup." 
 
练习删除default语句,看看会有什么错误?
 
运行switch中匹配到的子句之后,程序会退出switch语句,并不会继续向下运行,所以不需要在每个子句结尾写break。
 
你可以使用for-in来遍历字典,需要两个变量来表示每个键值对。 
  1. let interestingNumbers = [ 
  2.     "Prime": [2, 3, 5, 7, 11, 13], 
  3.     "Fibonacci": [1, 1, 2, 3, 5, 8], 
  4.     "Square": [1, 4, 9, 16, 25], 
  5. var largest = 0 
  6. for (kind, numbers) in interestingNumbers { 
  7.     for number in numbers { 
  8.         if number > largest { 
  9.             largest = number 
  10.         } 
  11.     } 
  12. largest 
 
练习:添加另一个变量来记录哪种类型的数字是最大的。
 
使用while来重复运行一段代码直到不满足条件。循环条件可以在开头也可以在结尾。
  1. var n = 2 
  2. while n < 100 { 
  3.     n = n * 2 
  4.   
  5. var m = 2 
  6. do { 
  7.     m = m * 2 
  8. while m < 100 
  
你可以在循环中使用..来表示范围,也可以使用传统的写法,两者是等价的:
  1. var firstForLoop = 0 
  2. for i in 0..3 { 
  3.     firstForLoop += i 
  4. firstForLoop 
  5.   
  6. var secondForLoop = 0 
  7. for var i = 0; i < 3; ++i { 
  8.     secondForLoop += 1 
  9. secondForLoop 
  
使用..创建的范围不包含上界,如果想包含的话需要使用...。
 

函数和闭包

使用func来声明一个函数,使用名字和参数来调用函数。使用->来指定函数返回值。 
  1. func greet(name: String, day: String) -> String { 
  2.     return "Hello \(name), today is \(day)." 
  3. greet("Bob""Tuesday"
 
练习:删除day参数,添加一个参数来表示今天吃了什么午饭。
 
使用一个元组来返回多个值。 
  1. func getGasPrices() -> (Double, Double, Double) { 
  2.     return (3.59, 3.69, 3.79) 
  3. getGasPrices() 
 
函数的参数数量是可变的,用一个数组来获取它们:
  1. func sumOf(numbers: Int...) -> Int { 
  2.     var sum = 0 
  3.     for number in numbers { 
  4.         sum += number 
  5.     } 
  6.     return sum 
  7. sumOf() 
  8. sumOf(42, 597, 12) 
  
练习:写一个计算参数平均值的函数。
 
函数可以嵌套。被嵌套的函数可以访问外侧函数的变量,你可以使用嵌套函数来重构一个太长或者太复杂的函数。
  1. func returnFifteen() -> Int { 
  2.     var y = 10 
  3.         func add() { 
  4.         y += 5 
  5.     } 
  6.     add() 
  7.     return y 
  8. returnFifteen() 
  
函数是一等公民,这意味着函数可以作为另一个函数的返回值。
  1. func makeIncrementer() -> (Int -> Int) { 
  2.     func addOne(number: Int) -> Int { 
  3.         return 1 + number 
  4.     } 
  5.     return addOne 
  6. var increment = makeIncrementer() 
  7. increment(7) 
 
函数也可以当做参数传入另一个函数。
  1. func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool { 
  2.     for item in list { 
  3.         if condition(item) { 
  4.             return true 
  5.         } 
  6.     } 
  7.     return false 
  8. func lessThanTen(number: Int) -> Bool { 
  9.     return number < 10 
  10. var numbers = [20, 19, 7, 12] 
  11. hasAnyMatches(numbers, lessThanTen) 
  
函数实际上是一种特殊的闭包,你可以使用{}来创建一个匿名闭包。使用in来分割参数并返回类型。
  1. numbers.map({ 
  2.     (number: Int) -> Int in 
  3.     let result = 3 * number 
  4.     return result 
  5.     }) 
 
练习:重写闭包,对所有奇数返回0。
 
有很多种创建闭包的方法。如果一个闭包的类型已知,比如作为一个回调函数,你可以忽略参数的类型和返回值。单个语句闭包会把它语句的值当做结果返回。
 
你可以获取参数的数量——这个方法在非常短的闭包中很有用。一个被作为最后一个参数传入函数的时候可以直接出现在括号后面。
  1. sort([1, 5, 3, 12, 2]) { $0 > $1 } 
 

对象和类

使用class和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。 
  1. class Shape { 
  2.     var numberOfSides = 0 
  3.     func simpleDescription() -> String { 
  4.         return "A shape with \(numberOfSides) sides." 
  5.     } 
 
练习:使用let添加一个常量属性,再添加一个接收一个参数的方法。
 
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
  1. var shape = Shape() 
  2. shape.numberOfSides = 7 
  3. var shapeDescription = shape.simpleDescription() 
 
这个版本的Shape类缺少了一些重要的东西:一个构造函数来初始化类实例。使用init来创建一个构造器。
  1. class NamedShape { 
  2.     var numberOfSides: Int = 0 
  3.     var name: String 
  4.   
  5.     init(name: String) { 
  6.         self.name = name 
  7.     } 
  8.   
  9.     func simpleDescription() -> String { 
  10.         return "A shape with \(numberOfSides) sides." 
  11.     } 
  
注意:self被用来区别实例变量。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像numberOfSides)还是通过构造器(就像name)。
 
如果你需要在删除对象之前进行一些清理工作,使用deinit创建一个析构函数。
 
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以忽略父类。
 
子类如果要重写父类的方法的话,需要用override标记——如果没有添加override就重写父类方法的话编译器会报错。编译器同样会检测override标记的方法是否确实在父类中。
  1. class Square: NamedShape { 
  2.     var sideLength: Double 
  3.   
  4.     init(sideLength: Double, name: String) { 
  5.         self.sideLength = sideLength 
  6.         super.init(name: name) 
  7.         numberOfSides = 4 
  8.     } 
  9.   
  10.     func area() ->  Double { 
  11.         return sideLength * sideLength 
  12.     } 
  13.   
  14.     override func simpleDescription() -> String { 
  15.         return "A square with sides of length \(sideLength)." 
  16.     } 
  17. let test = Square(sideLength: 5.2, name: "my test square"
  18. test.area() 
  19. test.simpleDescription() 
  
练习:创建NamedShape的另一个子类Circle,构造器接收两个参数,一个是半径一个是名称,实现area和describe方法。
 
属性可以有 getter 和 setter 。
  1. class EquilateralTriangle: NamedShape { 
  2.     var sideLength: Double = 0.0 
  3.   
  4.     init(sideLength: Double, name: String) { 
  5.         self.sideLength = sideLength 
  6.         super.init(name: name) 
  7.         numberOfSides = 3 
  8.     } 
  9.   
  10.     var perimeter: Double { 
  11.     get { 
  12.         return 3.0 * sideLength 
  13.     } 
  14.     set { 
  15.                 sideLength = newValue / 3.0 
  16.     } 
  17.     } 
  18.   
  19.     override func simpleDescription() -> String { 
  20.         return "An equilateral triagle with sides of length \(sideLength)." 
  21.     } 
  22. var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle"
  23. triangle.perimeter 
  24. triangle.perimeter = 9.9 
  25. triangle.sideLength 
  
在perimeter的 setter 中,新值的名字是newValue。你可以在set之后显示的设置一个名字。
 
注意EquilateralTriangle类的构造器执行了三步:
1. 设置子类声明的属性值
2. 调用父类的构造器
3. 改变父类定义的属性值。其他的工作比如调用方法、getters和setters也可以在这个阶段完成。
 
如果你不需要计算属性但是需要在设置一个新值之前运行一些代码,使用willSet和didSet。
 
比如,下面的类确保三角形的边长总是和正方形的边长相同。
  1. class TriangleAndSquare { 
  2.     var triangle: EquilateralTriangle { 
  3.     willSet { 
  4.         square.sideLength = newValue.sideLength 
  5.     } 
  6.     } 
  7.     var square: Square { 
  8.     willSet { 
  9.         triangle.sideLength = newValue.sideLength 
  10.     } 
  11.     } 
  12.     init(size: Double, name: String) { 
  13.         square = Square(sideLength: size, name: name) 
  14.         triangle = EquilateralTriangle(sideLength: size, name: name) 
  15.     } 
  16. var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape"
  17. triangleAndSquare.square.sideLength 
  18. triangleAndSquare.triangle.sideLength 
  19. triangleAndSquare.square = Square(sideLength: 50, name: "larger square"
  20. triangleAndSquare.triangle.sideLength 
  
类中的方法和一般的函数有一个重要的区别,函数的参数名只在函数内部使用,但是方法的参数名需要在调用的时候显式说明(除了第一个参数)。默认情况下,方法的参数名和它在方法内部的名字一样,不过你也可以定义第二个名字,这个名字被用在方法内部。
  1. class Counter { 
  2.     var count: Int = 0 
  3.     func incrementBy(amount: Int, numberOfTimes times: Int) { 
  4.         count += amount * times 
  5.     } 
  6. var counter = Counter() 
  7. counter.incrementBy(2, numberOfTimes: 7) 
 
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加?。如果?之前的值是nil,?后面的东西都会被忽略,并且整个表达式返回nil。否则,?之后的东西都会被运行。在这两种情况下,整个表达式的值也是一个可选值。
  1. let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square"
  2. let sideLength = optionalSquare?.sideLength 
 

枚举和结构体

使用enum来创建一个枚举。就像类和其他所有命名类型一样,枚举可以包含方法。
  1. enum Rank: Int { 
  2.     case Ace = 1 
  3.     case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten 
  4.     case Jack, Queen, King 
  5.     func simpleDescription() -> String { 
  6.         switch self { 
  7.         case .Ace: 
  8.             return "ace" 
  9.         case .Jack: 
  10.             return "jack" 
  11.         case .Queen: 
  12.             return "queen" 
  13.         case .King: 
  14.             return "king" 
  15.         default
  16.             return String(self.toRaw()) 
  17.         } 
  18.     } 
  19. let ace = Rank.Ace 
  20. let aceRawValue = ace.toRaw() 
  
练习:写一个函数,通过比较它们的原始值来比较两个Rank值。
 
在上面的例子中,枚举原始值的类型是Int,所以你只需要设置第一个原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。
 
使用toRaw和fromRaw函数来在原始值和枚举值之间进行转换。
  1. if let convertedRank = Rank.fromRaw(3) { 
  2.     let threeDescription = convertedRank.simpleDescription() 
 
枚举的成员值是实际值,并不是原始值的另一种表达方法。实际上,如果原始值没有意义,你不需要设置。
  1. enum Suit { 
  2.     case Spades, Hearts, Diamonds, Clubs 
  3.     func simpleDescription() -> String { 
  4.         switch self { 
  5.         case .Spades: 
  6.             return "spades" 
  7.         case .Hearts: 
  8.             return "hearts" 
  9.         case .Diamonds: 
  10.             return "diamonds" 
  11.         case .Clubs: 
  12.             return "clubs" 
  13.         } 
  14.     } 
  15.   
  16. let hearts = Suit.Hearts 
  17. let heartsDescription = hearts.simpleDescription() 
  
练习:给Suit添加一个color方法,对spades和clubs返回“black”,对hearts和diamonds返回“red”。
 
注意:有两种方式可以引用Hearts成员:给hearts常量赋值时,枚举成员Suit.Hearts需要用全名来引用,因为常量没有显式指定类型。在switch里,枚举成员使用缩写.Hearts来引用,因为self的值已经知道是一个suit。已知变量类型的情况下你可以使用缩写。
 
使用struct来创建一个结构体。结构体和类有很多相同的地方,比如方法和构造器。它们结构体之间最大的一个区别就是 结构体是传值,类是传引用。
  1. struct Card { 
  2.     var rank: Rank 
  3.     var suit: Suit 
  4.     func simpleDescription() -> String { 
  5.         return "The \(rank.simpleDescription()) of \ 
  6.         (suit.simpleDescription())" 
  7.     } 
  8. let threeOfSpades = Card(rank: .Three, suit: .Spades) 
  9. let threeOfSpadesDescription = threeOfSpades.simpleDescription() 
 
练习:给Card添加一个方法,创建一副完整的扑克牌并把每张牌的rank和suit对应起来。
 
一个枚举成员的实例可以有实例值。相同枚举成员的实例可以有不同的值。创建实例的时候传入值即可。实例值和原始值是不同的:枚举成员的原始值对于所有实例都是相同的,而且你是在定义枚举的时候设置原始值。
 
例如,考虑从服务器获取日出和日落的时间。服务器会返回正常结果或者错误信息。
  1. enum ServerResponse { 
  2.     case Result(String, String) 
  3.     case Error(String) 
  4.   
  5. let success = ServerResponse.Result("6:00 am""8:09 pm"
  6. let failure = ServerResponse.Error("Out of cheese."
  7.   
  8. switch success { 
  9. case let .Result(sunrise, sunset): 
  10.     let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)." 
  11. case let .Error(error): 
  12.     let serverResponse = "Failure...  \(error)" 
  
练习:给ServerResponse和switch添加第三种情况。
 
注意:如何从ServerResponse中提取日升和日落时间。
 

接口和扩展

使用protocol来声明一个接口。
  1. protocol ExampleProtocol { 
  2.     var simpleDescription: String { get } 
  3.     mutating func adjust() 
 
类、枚举和结构体都可以实现接口。
  1. class SimpleClass: ExampleProtocol { 
  2.     var simpleDescription: String = "A very simple class." 
  3.     var anotherProperty: Int = 69105 
  4.     func adjust() { 
  5.         simpleDescription += "  Now 100% adjusted." 
  6.     } 
  7. var a = SimpleClass() 
  8. a.adjust() 
  9. let aDescription = a.simpleDescription 
  10.   
  11. struct SimpleStructure: ExampleProtocol { 
  12.     var simpleDescription: String = "A simple structure" 
  13.     mutating func adjust() { 
  14.         simpleDescription += " (adjusted)" 
  15.     } 
  16. var b = SimpleStructure() 
  17. b.adjust() 
  18. let bDescription = b.simpleDescription 
  
练习:写一个实现这个接口的枚举。
 
注意:声明SimpleStructure时候mutating关键字用来标记一个会修改结构体的方法。SimpleClass的声明不需要标记任何方法因为类中的方法经常会修改类。
 
使用extension来为现有的类型添加功能,比如添加一个计算属性的方法。你可以使用扩展来给任意类型添加协议,甚至是你从外部库或者框架中导入的类型。 
  1. extension Int: ExampleProtocol { 
  2.     var simpleDescription: String { 
  3.     return "The number \(self)" 
  4.     } 
  5.     mutating func adjust() { 
  6.         self += 42 
  7.     } 
  8.  
  9. 7.simpleDescription 
 
练习:给Double类型写一个扩展,添加absoluteValue功能。
 
你可以像使用其他命名类型一样使用接口名——例如,创建一个有不同类型但是都实现一个接口的对象集合。当你处理类型是接口的值时,接口外定义的方法不可用。 
  1. let protocolValue: ExampleProtocol = a 
  2. protocolValue.simpleDescription 
  3. // protocolValue.anotherProperty  // Uncomment to see the error 
 
即使protocolValue变量运行时的类型是simpleClass,编译器会把它的类型当做ExampleProtocol。这表示你不能调用类在它实现的接口之外实现的方法或者属性。
 

泛型

在尖括号里写一个名字来创建一个泛型函数或者类型。
  1. func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] { 
  2.     var result = ItemType[]() 
  3.     for i in 0..times { 
  4.         result += item 
  5.     } 
  6.     return result 
  7. repeat("knock", 4) 
  
你也可以创建泛型类、枚举和结构体。
  1. // Reimplement the Swift standard library's optional type 
  2. enum OptionalValue<T> { 
  3.     case None 
  4.     case Some(T) 
  5. var possibleInteger: OptionalValue<Int> = .None 
  6. possibleInteger = .Some(100) 
  
在类型名后面使用where来指定一个需求列表——例如,要限定实现一个协议的类型,需要限定两个类型要相同,或者限定一个类必须有一个特定的父类。
  1. func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool { 
  2.     for lhsItem in lhs { 
  3.         for rhsItem in rhs { 
  4.             if lhsItem == rhsItem { 
  5.                 return true 
  6.             } 
  7.         } 
  8.     } 
  9.     return false 
  10. anyCommonElements([1, 2, 3], [3]) 
  
练习:修改anyCommonElements函数来创建一个函数,返回一个数组,内容是两个序列的共有元素。
 
简单起见,你可以忽略where,只在冒号后面写接口或者类名。<T: Equatable>和<T where T: Equatable>是等价的。

类型嵌套实例

下面这个例子定义了一个结构体BlackjackCard,用来模拟BlackjackCard(游戏:二十一点)中的扑克牌点数。BlackjackCard结构体包含2个嵌套定义的枚举类型 Suit 和 Rank。

 

在BlackjackCard规则中,Ace牌可以表示1或者11,Ace牌的这一特征用一个嵌套在枚举型Rank中的结构体Values来表示。

  1. struct BlackjackCard { 
  2.  
  3.     // 嵌套定义枚举型Suit 
  4.     enum Suit: Character { 
  5.        case Spades = "♠", Hearts = "?", Diamonds = "?", Clubs = "♣" 
  6.    } 
  7.  
  8.     // 嵌套定义枚举型Rank 
  9.     enum Rank: Int { 
  10.        case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten 
  11.        case Jack, Queen, King, Ace 
  12.        struct Values { 
  13.  
  14.            let first: Int, second: Int? 
  15.        } 
  16.        var values: Values { 
  17.        switch self { 
  18.        case .Ace: 
  19.             return Values(first: 1, second: 11) 
  20.         case .Jack, .Queen, .King: 
  21.             return Values(first: 10, second: nil) 
  22.         default
  23.             return Values(first: self.toRaw(), second: nil) 
  24.             } 
  25.        } 
  26.     } 
  27.  
  28.     // BlackjackCard 的属性和方法 
  29.     let rank: Rank, suit: Suit 
  30.     var description: String { 
  31.     var output = "suit is \(suit.toRaw())," 
  32.        output += " value is \(rank.values.first)" 
  33.         if let second = rank.values.second { 
  34.             output += " or \(second)" 
  35.         } 
  36.         return output 
  37.     } 

 

枚举型的Suit用来描述扑克牌的四种花色,并分别用一个Character类型的值代表花色符号。

 

枚举型的Rank用来描述扑克牌从Ace~10,J,Q,K,13张牌,并分别用一个Int类型的值表示牌的面值(这个Int类型的值不适用于Ace,J,Q,K的牌)。

 

如上文所提到的,枚举型Rank在自己内部定义了一个嵌套结构体Values。这个结构体包含两个变量,只有Ace有两个数值,其余牌都只有一个数值。结构体Values中定义了两个属性:

first, 为Int ;

second, 为 Int?, 或 “optional Int”;

 

Rank还定义了一个计算属性values,这个计算属性会根据牌的面值,用适当的数值去初始化Values实例,并赋值给values。对于J,Q,K,Ace会使用特殊数值,对于数字面值的牌使用Int类型的值。

 

BlackjackCard结构体自身有两个属性—rank与suit,它还定义了一个计算属性description,description属性使用rank和suit中的内容来构建对这张扑克牌名字和数值的描述,并且使用可选类型来检查是否存在第二个值,若存在,则在原有的描述中增加对第二数值的描述。

 

因为BlackjackCard是一个没有自定义构造函数的结构体,正如《Memberwise Initializers for Structure Types》中所描述的,BlackjackCard结构体有默认的成员构造函数,所以你可以使用默认的initializer去初始化新的常量theAceOfSpades:

  1. let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) 
  2. println("theAceOfSpades: \(theAceOfSpades.description)"
  3. // 打印出 "theAceOfSpades: suit is ♠, value is 1 or 11" 

 

尽管Rank和Suit嵌套在BlackjackCard中,但仍可被引用,所以在初始化实例时能够通过枚举类型中的成员名称(.Ace 和 .Spades)单独引用。在上面的例子中,description属性能正确地输出theAceOfSpades有1和11两个值。

 

类型嵌套的引用

在外部对嵌套类型的引用,是以被嵌套类型的名字为前缀,加上所要引用的属性名:

  1. let heartsSymbol = BlackjackCard.Suit.Hearts.toRaw() 
  2.  
  3. // 红心的符号 为 "?" 

 

对于上面这个例子,这样做可以使Suit, Rank, 和 Values的名字尽可能的简短,因为它们的名字会自然地由被定义的上下文来限定。



swift开发入门

阅读数 382

Swift的初认识

阅读数 285

没有更多推荐了,返回首页