• 使用swift开发OSX应用

    2016-01-03 18:33:49
    原文: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… 菜单...

    原文: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()方法——只要用户进行过新建、删除和编辑操作。

     





    展开全文
  • Swift开发Mac App(1)

    2017-06-28 10:41:24
    原文超级详细(图文),小白表示毫无压力:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1由于原文浅显到略显啰嗦,因此翻译时我有选择地删除了部分段落。打开Xcode,...

    原文超级详细(图文),小白表示毫无压力:http://www.raywenderlich.com/87002/getting-started-with-os-x-and-swift-tutorial-part-1

    由于原文浅显到略显啰嗦,因此翻译时我有选择地删除了部分段落。

    打开Xcode,使用 File\NewProject… 菜单,在弹出窗口中选择 “macOs/Coaca 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 as NSView).bounds

     

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

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

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




    展开全文
  • 第一阶段其实很蛋疼,牛人可能1个小时,半个小时,甚至10来分钟就能搞定了,而我却用了几天!...4. 在swift中调用xxx-touch库 5. 获取xxx-touch库中接收到的数据 6. 根据数据在界面上画出来按照思路应该

    第一阶段其实很蛋疼,牛人可能1个小时,半个小时,甚至10来分钟就能搞定了,而我却用了几天!这几天对我来说是挺打击的,自信心沉了一半……

    接着第一阶段的记录,继续……

    思路如下:

    1. 编译boost库(mac版本)
    2. 编译tinyxml库
    3. 编译xxx-touch库
    4. 在swift中调用xxx-touch库
    5. 获取xxx-touch库中接收到的数据
    6. 根据数据在界面上画出来

    按照思路应该走到第四步了,前三步都是准备工作,后面的3步才是最关键的,仔细想想,把后3步点思路细分(当时的方案):

    1. xcode怎么使用swift开发app呢?
    2. 在swift中调用c++的库,来获取设备返回的信息(这个使用一条单独的线程);
    3. 在swift中怎么使用线程(cocoa有个NSThread);
    4. 怎么画线,需要使用什么api完成画图的功能?;
    5. 将设备中的数据画出来(这个计划使用另外一条单独的线程)
    6. swift怎么在2条线程中进行数据同步?怎么使用锁?

    一、前期准备
    http://www.cocoachina.com/industry/20131211/7517.html
    https://developer.apple.com/library/mac/referencelibrary/GettingStarted/RoadMapOSX/books/RM_YourFirstApp_Mac/Articles/Introduction.html

    看了之后对mac下开发app有初步多认识,《Programming With Cocoa》第14、15、16章主要讲cocoa的图形介绍,前两章讲图形的基础,16章讲交互(可惜一致都找不到第16章),当然其他的也应该看;
    [当初为什么只看14 15章,因为对cocoa框架对了解基本是0,于是后面碰到了很多关于NSView的问题]

    在swift与objective c之间的选择

    mac上开发之前,对oc和swift做了对比,swift传说很强大,也是苹果新推出的语言,相必很有前途,于是就兴致勃勃到奔去学swift了。
    
    看了swift都语法感觉很熟悉,像脚步语言,也像java,也像ruby,不过个人觉得最像的却是c#,语法、委托、扩展函数等(当时很有印象的,久了却忘了-_-!!)。瞬间觉得很有亲切感,想起了以前做过的一个系统(在上一个公司做过一个管理系统,用c#开发的,不过做得很失败)。
    
    语法看了一遍,熟悉得差不多了,当然很多都没有记住,只是大概的知道而已;然后就开始干活了,怎么去调用之前编译好的库呢?查资料有点蛋疼啊...
    
    swift使用c很方便,却不支持c++。
    要使用c++就得先建个objective c的文件(CPPTouch.m),修改命名为CPPTouch.mm,(到了这一步只是oc与c++混编),swift还得访问需要通过oc才能访问调用c++到库
    
    本人觉得oc语法比较诡异,而swift语法又那么亲切,所以起初就选择到是swift。但是发现cocoa的库也是oc写的,oc的实例也比较齐全,而swift却比较少,而且调用c++那么失败,个人也比较喜欢cocos2d-x游戏的开发的,而cocos2d-x和c++,还得用oc,那干脆直接用oc好了。so,就这样不用swift了,改用oc,虽然语法是诡异,看多两下就好了;
    
    [个人的理解仍然比较肤浅,真想有大牛能指点迷津!]

    二、调用c++的库

    objective c 中调用c++只需将oc对.m后缀名改成.mm后缀即可调用c++的库。
    .m包含oc和c的特性,而.mm则包涵了则包含oc、c和c++的特性,3者兼容;

    混编,代码如下:

    - (void)cpp_print {
        std::cout << "This is a test." << std::endl;
        return;
    }

    三、使用线程

    -(void)startThread {
        thread = [[NSThread alloc] initWithTarget:self
                selector:@selector(cpp_print)
                object:nil];
        [thread start];
    }

    四、画线
    新建一个NSView的子类MyView,在xib的View中show the identity inspector > custom class > class中输入MyView即可;

    //
    //  CustomView3.swift
    //
    //  实现鼠标拖动画线的功能,当然这个只是前期做的实践
    //  因为前期计划是用swift开发,所以代码为swift的代码
    //  但是swift与oc是完全兼容的,只是语法上的差异而已
    
    import Cocoa
    
    class CustomView3: NSView {
        var last_point: NSPoint?
        var triangle: NSBezierPath = NSBezierPath()
    
        private var currentContext : CGContext? {
            get {
                if #available(OSX 10.10, *) {
                return NSGraphicsContext.currentContext()?.CGContext
                } else if let contextPointer = NSGraphicsContext.currentContext()?.graphicsPort {
                    let context: CGContextRef = Unmanaged.fromOpaque(COpaquePointer(contextPointer)).takeUnretainedValue()
                    return context
                }
    
                return nil
            }
        }
    
        private func saveGState(drawStuff: (ctx:CGContextRef) -> ()) -> () {
            if let context = self.currentContext {
                CGContextSaveGState (context)
                drawStuff(ctx: context)
                CGContextRestoreGState (context)
            }
        }
    
        func myThreadMethod2() {
            while(true) {
                // 划线
            }
        }
    
        override func drawRect(dirtyRect: NSRect) {
            super.drawRect(dirtyRect)
    
            // Drawing code here.
        }
    
        override func mouseDown(theEvent: NSEvent) {
            var mp: NSPoint = self.convertPoint(theEvent.locationInWindow, fromView: nil)
            self.last_point = mp
        }
    
        override func mouseUp(theEvent: NSEvent) {
            self.last_point = nil
        }
    
        override func mouseDragged(theEvent: NSEvent) {
            var mp: NSPoint = self.convertPoint(theEvent.locationInWindow, fromView: nil)
            saveGState { ctx in
                self.draw_line(mp)
                self.last_point = mp
                CGContextFlush(ctx)
            }
        }
    
        private func draw_line(point: NSPoint) {
            if (last_point != nil) {
                triangle.moveToPoint(last_point!)
                triangle.lineToPoint(point)
                triangle.stroke()
            }
        }
    }

    五、将各个功能组合起来的过程记录

    虽然这个程序很简单,画线的功能解决了,设备的数据也读取到了,但是要将这2个功能组合起来却始终有问题。
    起初,是按照这个思路来做的,线程1获取设备的数据后,另一条线程把获取的数据在view中绘制出来。但绘图时却出现了invalid context 0x0的错误;
    后来改用1条线程,读取到数据后直接绘制出来,依然存在invalid context 0x0;
    这个问题我能想到的原因:
    1. 不能在线程绘图;
    2. 线程里获取的图像上下文context无效,或者说该线程没有对应的view,所以获取不到context3. 另外还有个图像上下文的哪个API(好像是NSGraphicsContext)不支持osx10.104. 后来不知道哪里看到是:在主线程以外的线程绘图被系统禁止;
    
    想到线程与定时器的差别,换成NSTimer定时器会不会有效果,invalid context 0x0的错误依然出现。
    找度娘说有个StackView的东西,允许在线程里绘图,但是写了个StackView的子类后,直接编译错误;在苹果api的文档看到StackView只支持10.9以上点系统,那万一客户的系统版本比较老到时候就得重做了,于是放弃尝试StackView了;
    
    当时就停下来了,想着迷茫,线程又不行,定时器又不行,那搞个p啊,当时老大已经找了我好几遍,我却还卡在这个问题上,又没有朋友是做这方面开发的,心里面很是着急,无奈,无助;
    
    但无奈归无奈,叹息归叹息,问题始终是要解决,静下来查资料。想到不知道回调函数行不行。在线程里面获取数据,然后数据回调到View控制器中,View控制器在主线程,那这样应该可行,于是就去尝试了。
    结果这个卡了1天点问题终于解决了,那时的心情真无法用言语来形容;至此需求行的功能已经完成了;
    #import <Foundation/Foundation.h>
    
    @protocol TouchDelegate <NSObject>
    @required
    -(void)successful:(size_t)id setP:(size_t)phase setX:(double)x setY:(double)y setW:(double)width setH:(double)height;
    @end
    #import <Foundation/Foundation.h>
    #import "TouchDelegate.h"
    
    @interface CTouch : NSObject {
        id<TouchDelegate> touchDelegate;
        NSThread* thread;
    }
    -(void)startThread;
    @property (nonatomic,retain) id<TouchDelegate> touchDelegate;
    @end
    #import "CTouch.h"
    
    @implementation CTouch
    @synthesize touchDelegate;
    
    -(void)startThread {
        thread = [[NSThread alloc] initWithTarget:self
                selector:@selector(get_touch)
                object:nil];
        [thread start];
    }
    
    - (void)print_event:(xxxxx*)event {
        if (!event) return;
    
        if (self.touchDelegate!=nil) {
            //完成线程 调用回调函数
            [self.touchDelegate successful:event->id
                  setP:event->phase
                  setX:event->x
                  setY:event->y
                  setW:event->width
                  setH:event->height];
        }
        return;
    }
    //......
    @end
    #import "TouchView.h"
    
    @implementation TouchView
    
    - (id)initWithCoder:(NSCoder *)coder {
        array = [NSMutableArray arrayWithCapacity:100];
        tmp = [NSNumber numberWithFloat:0.0];
        for (int i = 0; i < 100; i++) {
            [array addObject:tmp];
        }
    
        touch = [[CTouch alloc] init];
        //通知调用协议
        touch.touchDelegate = self;
        [touch startThread];
        // 获取屏幕分辨率
        screenRect = [[NSScreen mainScreen] frame];//]visibleFrame];//frame];
        // NSLog(@"Screen %@", NSStringFromRect(screenRect));
        screen_width = screenRect.size.width;
        screen_height = screenRect.size.height;
    
        return self;
    }
    
    - (void)drawRect:(NSRect)dirtyRect {
        [super drawRect:dirtyRect];
    
        // Drawing code here.
    
        // 画布(上下文)初始化
        context = [[NSGraphicsContext currentContext] graphicsPort];
        [self drawTest:300 setY:300];
        [self cleanScreen];
        //[self drawTest:200 setY:300];
    }
    
    -(void)successful:(size_t)id setP:(size_t)phase setX:(double)x setY:(double)y setW:(double)width setH:(double)height {
        if (phase == 2) {
            [self cleanLastPoint:id];
        } else {
            [self drawLine:id setX:x setY:y];
            [self saveLastPoint:id setX:x setY:y];
        }
    }
    
    -(void)drawLine:(size_t)id setX:(double)x setY:(double)y {
        if ([array[id * 2] floatValue] > 0) {
            double lx = [array[id*2] floatValue];
            double ly = [array[id*2+1] floatValue];
            CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
            CGContextMoveToPoint(context, lx * screen_width, (1-ly) * screen_height);
            CGContextAddLineToPoint(context, x * screen_width, (1-y) * screen_height);
            CGContextStrokePath(context);
            CGContextFlush(context);
        }
    }
    
    -(void)saveLastPoint:(size_t)id setX:(double)x setY:(double)y {
        NSNumber* myx= [NSNumber numberWithFloat:x];
        NSNumber* myy= [NSNumber numberWithFloat:y];
        if (id < [array count]) {
            array[id * 2] = myx;
            array[id * 2 + 1] = myy;
        }
    }

    六、清除绘制的线条
    这个问题也被卡了好久,就像一个那么小的功能也被卡那么久,严重怀疑自己的能力;期间老大也来找我几次,令我的自信心受到了重创;

    七、NSView获取不到键盘事件和鼠标事件
    事件其实跟绘图类似,绘图是在view点前提,而事件也一样;
    捕捉不到到情况:只有一个mainmenu.xib和一个NSView的子类;加个xxxViewController就好了;大概就是一个事件依赖于一个实体(这里应该就是ViewController),实体不存在,事件也不存在,事件才能由NSResponse 传递到View中,并在view中得到响应;

    八、清屏乱刷
    这也是自己程序的问题吧,在AppDelegate中定义了一个捕捉事件到循环,在循环中捕捉键盘事件,当读到esc时程序退出;另外非esc按键多响应则说CGContextFlush(context);也是因为这句导致清屏凌乱、甚至无法清屏的bug、按下任意键后无法绘图bug的原因;

    在View中也有捕捉键盘事件到响应,ViewController也有,太蛋疼了;删除所有键盘响应的代码,只保留View中的响应,用于清屏(真正的清屏功能是在View里面实现的)

    九、xxx-touch库的bug
    当所有功能都做好了,发现了一个bug,某个条件内没有数据;觉得库的问题应该是写库的那个人改吧;汇报之后调bug的任务又落在我身上了!没有看过这个库的代码,只是拿来编译使用,觉得调这个bug没有什么信心;单步调试都没有结果,却忽视了另外一条线程对存在;原来这个库也用了2条线程,一个读取设备状态生成事件到队列,另一条线程处理队列中的事件;因为这事我觉得老大对我的印象都变差了,有点伤;

    十、打包
    1. 打包则有很多种方法,最初用了系统自带的dmg镜像;这个很简单,把app放在一个文件夹,然后使用“磁盘工具”直接做成dmg镜像;
    2. PackageManage Packages Iceberg等软件都能使用,但是觉得PackageManage很费劲,不知道什么时候开始xcode默认不自带了,系统也不自带,却集成到auxiliary tools里了,需要自己下载安装,我下了4个,有的文件不齐,有的打开时提示损坏;
    3. Iceberg安装则很方便,但是打开软件一直没有反应,用不了,蛋疼;
    4. Packages,下载完成后能安装,能运行,但我不会用,看着一页页长长的英文说明文档,就觉得很浮躁,旁边的人说话觉得特别刺耳,,,,,,,,,,最后还是实现了最简单的打包功能了;本机测试通过,待其他机子测试安装包和程序;

    至此,这个小小点项目算是完成了;终于放下心头大石了,我庆幸我坚持住了!虽然这个纪录文档(不算技术文档,只是用来记录这个过程,那些错误的,因为过程太蛋疼了,所以需要记录,好记性不如烂笔头,下次遇到这样的问题就不怕了)。

    反思:
    基本知识不扎实,时间都花在解决问题上面,没有系统的去研究(还有不知道怎么去研究,也没有时间去研究——可能这是借口吧)
    要学会学习、学会分析、学会百度 google bing

    展开全文
  • Swift开发Mac App(6)

    2015-05-07 09:28:47
    接下来实现EDStarRating 的编辑。 在loadView 方法中,我们已经配置了...打开MasterViewController.swift 在 EDStarRatingProtocol 扩展中添加如下方法: func starsSelectionChanged(control: EDStarRating!,

    接下来实现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按钮,所有数据又恢复原样。


    展开全文
  • Swift开发Mac App(3)

    2015-04-22 14:00:25
    显示昆虫列表在 OS X中,Table View使用 NSTableView类,它等同于iOS的UITableView 类,但有一个最大的不同是:NSTableView 的每一行有多个列或多个单元格。· 在OS X 10.7Lion之前,table view cell继承于NSCell...

    显示昆虫列表

    在 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) as NSTableCellView      

    // 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 文件夹中:



    展开全文
  • Swift开发Mac App(8)

    2015-05-15 21:23:16
    关注细节关于用户体验方面,我们仍然有一些细节值得注意。...作为程序员,你当然知道当用户什么都没选择的情况下,不应当执行任何操作,但对于用户而言,这种情况仍然显得不太友好:我们通过以下方式来解决这个问题:·...
  • Swift开发实战权威指南》是欧阳大神根据自己多年互联网实战经验,结合自己独特视角专为swfit从事人员打造,是帮你从入门到精通的最全、最详、最新颖、最深入的swift开发手册。 作者简介 欧阳坚(欧阳大神)...
  • 【导语】Swift 自发布以来就备受众多 Apple 开发者关注,但由于 API 尚不稳定,系统没有内置 Framework 导致 App 包增大等问题,使得线上主力使用的公司还很少,不少客户端开发者都还没有机会使用 Swift 进行开发。...
  • 文章转载自我的个人博客原文链接自言自语: 亿万千百十, 皆起于一资料1 —...Github上的地址点我Using Swift with Cocoa and Objective-CWWDC 2015 系统化的开发文档iOS Developer LibrarySwift 开源及跨平台开发swift
  • 用于为macOS Mojave创建动态壁纸的控制台应用程序
  • http://www.itdaan.com/keywords/Show+NSMenu+next+to+NSButton+in+Swift+OSX.html http://www.itdaan.com/keywords/Swift+Mac+OSX+NSButton+title+color.html 1在SwiftOSX中显示NSButton旁边的NSMenu - ...
  • Swift是一门用于开发iOS和OSX应用程序的新语言,基于C和Object-C,但是没有C兼容性的限制。Swift采用安全的编程模式,并增加了许多新的现代模式,让编程更加的容易、灵活,让编程更加有乐趣。Swift被目前成熟并很受...
  • swift开发入门

    2014-06-04 14:21:37
     目录 1 简介2 Swift入门3 简单值4 控制流5 函数与闭包6 对象与类7 枚举与结构 ...今天凌晨Apple刚刚发布了Swift编程...希望对各位的iOS&OSX开发有所帮助。 Swift是供iOS和OS X应用编程的新编程语言
  • 下面开始介绍如何使用Swift开发一个Mac Menu Bar(Status Bar) App。通过做一个简单的天气App。天气数据来源于OpenWeatherMap。完成后的效果如下: 01开始建立工程打开Xcode,Create a New Project or File - New ...
  • 本文主要分享下楼主在学习Swift编程过程中,对GitHub上的一个开源App Swift Music的研究心得。 项目地址:https://github.com/xujiyao123/SwiftMusic 一、项目简介 本项目主要实现了歌曲关键字查询歌曲,...
  • Swift代码语言教程:在刚刚过去的WWDC2014大会上,苹果公司新发布了一种编程语言Swift。据悉,Swift语言继承了C语言以及Objective-C的特性,且克服了C语言的兼容性问题,对于广大开发者来说,这无疑是一剂难以抗拒的...
  • MAC OSX APP 开发入门篇

    2019-03-21 20:21:38
    MAC OSX APP 开发入门篇 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变...
  • 对JavaScript开发者来说,Swift将会是一种看上去比较熟悉的高级程序设计语言,不过它是用LLVM编译生成OSX和iOS上的高性能可执行代码的。\\苹果已经在LLVM技术上进行了巨大的投入,这种技术提供了一个抽象的指令集,...
  • 老实说:第一个swift语句的ios...本人开发环境:mac osx 10.10 dp1 + xcode beta + swift(更低xcode不支持swift语言)   思路与大致过程:  1、建立工程,工程包含界面文件,代码文件等等  2、在工程
  • 很长一段时间,Objective-C是用于创建OSX和iOS应用程序的主要编程语言。Objective-C基本上是C的超集,增加了面向对象的特性和动态运行时。2014年,Apple推出了一种名为Swift的新编程语言,它被描述为“Objective-C ...
1 2 3 4 5 ... 20
收藏数 1,516
精华内容 606