[翻]OS X和Swift入门指南:(2/3)

本片翻译自:http://www.raywenderlich.com/87003/getting-started-with-os-x-and-swift-tutorial-part-2

这个教程是如何在OSX通过Swift创建简单App三部分系列教程的第二部分。
第一部分教程中,你创建了一个Mac应用程序显示了一个Scary Bugs列表。
在第二部分,你将会学习如何添加详细部分来显示关于bug的全部信息:它的名字,恐怖得分,并且一个关于bugs的大图片(并且更加恐怖!)。
你也会学习到如何改变信息,评分,甚至修改它们的图片!

让我们开始吧!

下载一些东西

为了完成这个教程,你需要一些图片和一些代码文件。所以下载这些额外的工程资源并且解压它们。
注意:为了给bugs从 “not scary” 到 “totally scary” 评分。你将会使用开源的评分控件,叫做EDStarRating,它在包中已经包含了。
在这个教程中,我们不会解释它如何实现的,但是我们将会教你如何在你的工程中使用自定义控件。这个包中同样包含了一个NSImage category,你将会用来创建大bug图片的缩略图,还有三种表情来显示评分。
对于EDStarRating的更多信息,你可以查看它的github页面。

如你在系列教程的第一部分所做的,你将会创建一些group来组织事物。
首先,创建新的group命名为“Art”。拖拽三个shocked face图片到这个新的分组中--这些图片是评分视图的“stars”,为了一些幽默。确定“Copy items if needed”被选中,并且“Add to targets节中的“ScaryBugsMac”被选中。
在Xcode中创建另一个group命名为“Views”,然后选择并且拖拽 EDStarRating.hEDStarRating.m到这个分组总。同样确保“Copy items if needed”被选中, 并且“Add to targets节中的“ScaryBugsMac”被选中。这个是五星评分视图。
osx--tutorial-importfile1
点击完成,在下一个屏幕中,当询问“Would you like to configure an Objective-C bridging header?”时确认选择了“Yes”。bridging header是Xcode用来在Swift代码中引用Object-C类用的。
osx-tutorial-bridingheader
NSImage+Extras.hNSImage+Extras.m做同样的操作,除了拖拽他们到新的分组命名为“Helpers”。这是一些帮助代码,你将会用来稍后缩放图片。同样,确保所有需要的检查和选择(Copy items if needed 和 Add To targets)。
下一步,打开ScaryBugsMac-Bridging-Header.h文件,添加如下导入声明:

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

Bridging headers和在同一工程中混合使用Object-C和Swift和iOS的工作方式一样。
这是我们的工程在添加这些文件后的显示效果,同时移动bridging header到Supporting Files group:
osx-tutorial-project-imports

创建详细Section

现在是时候修改用户界面,包含用来显示ScaryBugs详细信息的控件。
在iOS中,典型的Master/Detail应用程序创建两个view,当点击table的某一行时,界面跳转到不同的视图来显示需要的信息。
在OS X中,样式是不同的。因为你没有iPhone屏幕的大小限制,你可以在同样的view中添加详细信息。你只需要调整它的大小更大一些。类似于iPad的Master/Detail应用程序形式。
打开MasterViewController.xib。选择view,并且增加它的宽度和高度。你不需要担心额外的大小。只需要适应你将要在右侧添加的控件的大小。
像如下所示:
osx-tutorial--resize-masterview
下一步是添加需要的控件。你需要用来显示如下详细信息:bug的名字,评分和图片。
对于Bug的名字你将会使用一个NSTextField控件,它可以显示并且编辑文本。对于评分,你将会使用EDStarRating控件。对于图片,你将会使用一个NSImageView
除了这些,你也会添加两个带标题的label,仅仅是为了让用户知道每个区域的意思。
为了这么做,你只需要做你以前在table view上做的操作。在屏幕右下角的Controls Panel找到需要的控件,并拖拽他们到我们的视图中。
拖拽一个Text Field(作为名字),两个Labels(作为标题),一个Image View(在Controls Panel叫做‘Image Well’)。
EDStarRating是一个自定义控件,所以在panel中没有包含。为了使用它,你需要在Controls Panel中查找 名为“Custom View”的控件。拖拽它到主视图。稍后你将会配置它来显示评分。
现在,是时候排列控件了。将他们放在view的右侧空白区域。从头到脚垂直排列,如下方式:

  • 首先,一个Label,用于名字的标题。在下面,放置text filed。
  • 在text filed下面是第二个Label(将会作为评分的标题)
  • 在第二个Label下面,放置custom view(将会作为我们的评分控件)
  • 最后,在custom view下面放置image view。
  • 尝试调整控件使它们的左侧对齐。完成后,你的视图应该和下面的类似:
    osx-tutorial-emptydetail
    在前面的步骤中,你为EDStarRating添加了一个custom view。现在你需要告诉Interface Builder你想要view类是EDStarRating,替代基础的NSView
    你们怎么做到这样?很简单。选红你的custom view。在Utilities面板中,通过点击第三个tab来切换到Identity Inspector。
    在这个tab中,修改类的名字为EDStarRating
    osx-tutorial-edstarrating
    现在你需要修改labels的文本。选择第一个label,通过点击第四个tab切换到Attributes Inspector。然后,修改标题为“Name”。
    注意,你也可以通过双击label来修改文本,直接修改label的文本。
    对第二个label重复这个步骤,修改标题为 “Rating”。
    最后一步是配置主窗体,因此新的控件可以在app运行时显示。在稍后的教程中,你将会使用auto layout来保证view大小改变。
    选择顶层view(在document outline中也叫做 “Custom View”)并且在Size Inspector记住它的宽度和高度(那个有尺子图标的)。
    osx-tutorial-static_window_sizing_src
    然后切换到MainMenu.xib,选择ScaryBugsMac窗体,并且设置窗体的宽高同刚才在Size Inspector相同值。同时选中Minimum Size选择框。
    osx-tutorial-static_window_sizing_dest
    好了,现在是时候编译并且运行应用程序了。如果一切正常,应用程序将会运行,并且你应该看到如下窗体:
    osx-tutorial-smats-app2-1
    你可以看到所有的空间都在那里了,但是评分空间没有显示。别担心这个-控件在那里,但是因为你还没有配置它,所以它没有显示任何东西。
    view已经包含了你用来显示详信息的全部控件。现在你需要为view controller添加outlets,因此你可以稍后在我们的代码中访问这些控件。
    为了这么做,返回Xcode,并且打开MasterViewController.xib,切换到Assistant Editor (在 头部工具栏的“Editor”section中的第二个按钮),并且确保assistant显示MasterViewController.swift
    选择table view(记得吧,你需要点击两次,或者在左侧的Document outline中选择)。当你确保选择了table view而不是scroll view时,control-drag 从table view到MasterViewController.swift,在类的开始括号之后:
    osx-tutorial-bugsTableView
    将会显示一个弹窗来允许你为类添加NSTableView的outlet。命名为bugsTableView,确保Storage设置为Weak,然后点击Connect。
    osx-tutorial-bugsTableView2
    通过简单的操作,你已经为view controller添加了一个属性,它的内部连接到Interface Builder中的table view。
    看在你需要对text filed和image view重复这些步骤(但是不需要对label重复-label的文本不需要改变,所以你不需要添加这些outlets)。
    你只需要做先前的痛痒步骤。选择text filed,并且control-drag从它到MasterViewController.swift。命名outlet为bugTitleView
    对image view重复同样的步骤,命名outlet为bugImageView。并且对于最后一个控件,bug评分自定义控件,创建outlet并且命名为bugRating
    在创建这些outlets之后,你应该在MasterViewController.swift有如下列表:

    @IBOutlet weak var bugsTableView: NSTableView!
    @IBOutlet weak var bugTitleView: NSTextField!
    @IBOutlet weak var bugImageView: NSImageView!
    @IBOutlet weak var bugRating: EDStarRating!
    

    你将发现到这里没有关于EDStarRating引用的警告或错误-它已经通过bridging header被引用,并且对于Swift文件是可见的。

    显示详细信息

    现在,是时候在你刚刚添加的控件中显示一些信息了。当用户点击table view的任何行时,你需要得到选择的bug信息,并且在details section中显示这些信息。
    这包括三个步骤:
    1、你需要知道选择了哪行。table view通过调用tableViewSelectionDidChange方法来告诉delegate哪行被选中。所以你需要在MasterViewController中实现这个方法来接收通知。
    2、你需要从bugs数组中得到选择的Scary Bug。
    3、最后,你需要使用选择的Scary Bug在details section中显示信息。

    打开MasterViewController.swift,添加如下方法到类中:

    func selectedBugDoc() -> ScaryBugDoc? {
      let selectedRow = self.bugsTableView.selectedRow;
      if selectedRow >= 0 && selectedRow < self.bugs.count {
        return self.bugs[selectedRow]
      }
      return nil
    }
    

    这个方法首先得到了table view选择的行。在做一些正常检查后,你通过索引bugs数组,得到了ScaryBugDoc的位置。
    下一步,添加如下代码:

    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中读取了标题,评分和图片,并且传递信息到控件。为了修改Bug标题,你设置了bugTitleView控件的stringValue属性。为了修改图片,你设置了bugImageView控件的image属性。然后,你通过bugRating控件的rating属性设置评分。
    这就是了!在添加这些简单代码后,你现在可以为选中的bug显示详细信息了。
    下一步,在文件的结尾附近,添加如下代码作为NSTableViewDelegate扩展:

    func tableViewSelectionDidChange(notification: NSNotification) {
      let selectedDoc = selectedBugDoc()
      updateDetailInfo(selectedDoc)        
    }
    

    这里你所做的全部就是调用上面的两个helper方法得到选择的bug,并且每当用户选择新的table行时更新显示。
    首先你需要做的是配置评分控件。你需要设置它的一些属性来以你需要的方式工作-并且你需要在view显示在屏幕之前做。
    对于OS X 10.10 Yosemite,添加了新的view controller生命周期方法viewWillAppear, viewDidLoad。典型的在代码中设置你视图的方法是loadView(),并且是向后兼容的。
    你可以重写这个方法,并且添加你需要的任何初始化配置。在方法开头调用父类的实现是重要的,也许view将不会被创建!
    添加如下方法:

    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)
    }
    

    这里你设置了评分控件需要的默认值:用户选择评分显示的图片,delegate,和view的参数。
    你将会注意到Xcode有一个错误,你还没有实现EDStarRatingProtocol。在MasterViewController.swift最后添加如下代码来修正这个错误:

    // MARK: - EDStarRatingProtocol
     
    extension MasterViewController: EDStarRatingProtocol {
     
    }
    

    你将在教程的后面实现EDStarRatingProtocol方法。
    现在,编译运行应用程序。你应该看到在评分控件中的funny faces。因为你已经成功配置它了。
    同时,现在,如果你点击任何行,选择bug的信息和图片会显示在details section!
    osx-tutorial-ladybug1

    真棒,现在这个app看着像个真正的app了!
    在这里,你可以选择列表中的bug并且显示它的详细信息。你也可以在detail section中修改文本和评分;然后,在你修改后,没有修改会映射到bug list和model中!为了让这个程序功能齐全,你想要添加新的bug或者删除已经窜爱的bugs,所以在下一个section中,将会包含如何添加这些编辑特性到你的app中!

    添加和删除bugs

    现在,是时候实现编辑功能了。首先,你需要添加两个按钮:一个添加新行,另一个删除选择的行。
    打开MasterViewController.xib在controls panel中找到“Gradient Button”。在table view下拖拽两个这类的按钮。如果有需要修改table view的大小。
    选择其中一个按钮,打开Attributes Inspector(Utilities Panel的第四个tab)。这将是“Add row”按钮。对于这些按钮,你将会使用系统图片而不是设置标题。
    在attributes inspector中,定位了到Title属性,删除全部文本。它应该是空的,所以按钮步显示任何文本。现在在Image属性中,你将会选择一个“+” 符号的图片。
    这个图片叫做“NSAddTemplate”。你可以输入名字,或者点击组合框,向下滚动直到你找到它。
    完成后,对另一个按钮重复同样的操作。删除它的标题,设置图片属性为“NSRemoveTemplate”,它是一个“-” 符号的图片。
    osx-tutorial-addbutton
    在对按钮使用这些图片后,你也许想要修改他们的大小,使它们小一点。
    现在你有了两个按钮,但是它们没有做任何事情,因为你还没有把它们和view controller连接起来。
    你需要为每一个按钮添加一个action。这个几乎和你为控件创建properties一样,并且和iOS也一样。
    同样,切换到Assistant Editor,并且确保显示MasterViewController.swift
    MainViewController中为按钮actions添加一个新的空extension。如下:

    // MARK: - IBActions
     
    extension MasterViewController {
     
    }
    

    extension的操作不是严格必须的,但是提供了帮助你组织你的Swift view controller代码的方便方式,如同你对每个protocol做的操作。
    下一步,选择 “Add” 按钮,control-drag从按钮到MasterViewController.swift新的extension,
    osx-tutorial-addBug1

    一个弹窗将会显示,允许你为按钮创建新的action。确保连接类型选择Action。命名Action为addBug,然后点击连接。
    osx-tutorial-addBug2
    在完成之后,在view controller中一个新的addBug(_:) 方法被创建了。每次用户点击添加Bug的按钮,系统就会调用这个方法。
    对于删除按钮重复同样的操作,并且命名action为deleteBug
    是时候为添加删除bugs方法增加代码了。打开 MasterViewController.swiftaddBug内添加如下代码:

    // 1. Create a new ScaryBugDoc object with a default name
    let newDoc = ScaryBugDoc(title: "New Bug", rating: 0.0, thumbImage: nil, fullImage: nil)
     
    // 2. Add the new bug object to our model (insert into the array)
    self.bugs.append(newDoc)
    let newRowIndex = self.bugs.count - 1
     
    // 3. Insert new row in the table view
    self.bugsTableView.insertRowsAtIndexes(NSIndexSet(index: newRowIndex), withAnimation: NSTableViewAnimationOptions.EffectGap)
     
    // 4. Select the new bug and scroll to make sure it's visible
    self.bugsTableView.selectRowIndexes(NSIndexSet(index: newRowIndex), byExtendingSelection:false)
    self.bugsTableView.scrollRowToVisible(newRowIndex)
    

    让我们看看在这里做什么。
    为了设置mode方面,你首先创建了新的ScaryBugDoc对象,并且添加它到你的bugs数组。
    在view方面,你为这个bug在talbe view添加了新的行。完成后,操作系统会自动调用tableView(_:viewForTableColumn:row:)方法,并且cell会根据Bug信息进行更新。
    最后两个只是为了美化来添加。你选择了你创建的行,并且确保table view滚动到那行,因此,新创建的行是可见的。

    并且,现在对于删除按钮,在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)
    }
    

    在这个方法中,你首先使用optional绑定到selectedDoc()调用,这个你先前写好用来得到当前选择bug的-这就是可复用代码的优势!
    如果没有选中,这个方法会返回nil,所以,如果这里有选中的bug,你从你的数据中移除这个bug。在最后一行,你仅仅通过nil更新detail section。
    这就是全部-编译运行它!如果一切正常,现在,当你点击添加Bug按钮,新的一行被添加了。
    你也可以通过选中一行并且点击删除按钮删除bug。
    osx-tutorial-app2-2-addBug

    编辑bug信息

    在这里,你可以从列表添加和移除Scary Bugs。现在是时候编辑已经存在的bugs。
    你可以对bug做三种改变:修改它的名字,评分,和图片。
    首先,让我们看看如何修改名字。当你选择一个bug,它的名字在detail section中的text filed中。
    现在你可以修改它,但是修改没有被存储在model中,所以,这些修改丢失了。你需要在每次用户修改选择的bug时更新model。
    为了完成它,首先你需要知道什么时候文本被修改了。你不想在用户每次修改字符时都得到通知,而是在用户完成编辑后得到通知。
    这个是在用户点击ENTER,或者用户从text filed切换到另一个控件时发生。text filed当这个发生时发送一个action,同按钮在点击后发送的action。
    所以,实现的方式是一样的。打开MasterViewController.xib,切换到Assistant Editor,并且确保显示MasterViewController.swift
    选择text filed,control-drag从它到MasterViewController.swift,在addBug()之前:
    osx-tutorial-titleChange1
    一个弹窗将会被显示出来,允许你为text filed创建新的action。命名action为bugTitleDidEndEdit
    osx-tutorail-titlechange2
    这个方法会被操作系统在用户完成编辑文本后调用。返回MasterViewController.swift,为类添加如下帮助方法:

    func reloadSelectedBugRow() {
      let indexSet = NSIndexSet(index: self.bugsTableView.selectedRow)
      let columnSet = NSIndexSet(index: 0)
      self.bugsTableView.reloadDataForRowIndexes(indexSet, columnIndexes: columnSet)
    }
    

    这个方法会触发重新加载table view的单行来更新数据。当你修改model时,你也需要调用这个方法让table view row更新自己。
    下一步,找到bugTitleDidEndEdit,添加如下方法实现:

    if let selectedDoc = selectedBugDoc() {
      selectedDoc.data.title = self.bugTitleView.stringValue
      reloadSelectedBugRow()
    }
    

    首先通过调用selectedBugDoc()得到选择的bug。然后,你得到text field新的文本,然后修改document中bug的标题。
    最后一步是在reloadSelectedBugRow()修改table view的标题。为了这么做,你只需要告诉table view重新加载当前bug的行。这个将会导致 tableView(_:viewForTableColumn:row:)被调用,它将会适当的重新加载table view cell。
    注意:更好的方式是通过重新加载它来更新cell,而不是直接在tableView(_:viewForTableColumn:row:)外直接操作cell的内容。
    编译并且运行应用程序。现在,如果你选择了一个bug,并且编辑了它的名字(记得点击Enter),table view中的名字将会修改!
    如果你切换当前选择的section,并且切换回来,新的文本仍然在那里,因为,现在你已经在model对象中存储它了。
    osx-toturial-app2-3-newname
    现在,是时候修改评分了。EDStarRating工作方式和table view类似。你需要定义它的delegate,并且操作系统将会调用这个方法来通知你评分改变了。
    你已经在先前loadView方法中配置这些了,所以,你只需要添加将要调用的方法。
    打开MasterViewController.swift,在EDStarRatingProtocol中添加如下extension:

    func starsSelectionChanged(control: EDStarRating!, rating: Float) {
      if let selectedDoc = selectedBugDoc() {
        selectedDoc.data.rating = Double(self.bugRating.rating)
      }
    }
    

    这里你做的同以前一样:你得到选择的doc,并且更新它为新的值。
    编译运行应用程序。现在注意到评分值已经被存储在model中了,并且每次你修改Bug的评分,这个值被保存了,即使你选择了其他bug并且稍后重新选择第一个bug。
    osx-tutorial--app2-4-newRating
    所以,只剩下一件事要做了-允许用户修改bug的图片!

    得到一个图片

    为了从用户那里得到新的图片,你将要添加一个新的按钮。当用户点击它时,你将弹出一个窗口允许用户选择新的图片。
    打开MasterViewController.xib。在object library中找到“Push Button”控件,拖拽它到view中,在image view下面。
    修改按钮的标题为 “Change Picture”:
    osx-tutorial-changePictureButton
    现在,你需要为它添加action。同你对“Add”和“Delete” 按钮做的操作,命名action为changePicture
    这个action将会在你每次点击按钮时调用。下一步是向用户显示一个窗口来修改bug的图片。
    为了实现,你需要使用一个OS X特殊的控件,叫做IKPictureTaker。这个控件允许你从你的电脑选择一张图片或者通过摄像头拍照,并且仅仅通过一行简单的代码。你可以在Apple’s ImageKit Programming Guide学习更多关于控件的知识。
    一旦用户选择图片,控件将会通过delegate回调,通知你的view controller一张图片是有效的。
    选择MasterViewController.swift,并且在文件头部添加导入:

    import Quartz
    

    image picker是Quartz framework的一部分,所以你需要首先实现它。
    现在,在changePicture中添加如下代码:

    if let selectedDoc = selectedBugDoc() {
      IKPictureTaker().beginPictureTakerSheetForWindow(self.view.window,
        withDelegate: self,
        didEndSelector: "pictureTakerDidEnd:returnCode:contextInfo:",
        contextInfo: nil)
    }
    

    在这段代码中,你首先需要检查是否有bug被选中。如果你找到了一个bug,然后你显示图片选择控件,因此用户可以选择一张图片。
    通过这行代码,ImageKit将会显示一个窗体选择新的图片。在同样的方法中,你告诉图片选择控件,view controller(self)将会是它的delegate。
    当这个完成后,picture taker控件将会调用delegate方法pictureTakerDidEnd(_:returnCode:contextInfo:)。在这个方法中,你将会收集新的图片,并且添加它到bug中。
    添加如下方法:

    func pictureTakerDidEnd(picker: IKPictureTaker, returnCode: NSInteger, contextInfo: UnsafePointer<Void>) {
      let image = picker.outputImage()
     
      if image != nil && returnCode == NSModalResponseOK {
        self.bugImageView.image = image
        if let selectedDoc = selectedBugDoc() {
          selectedDoc.fullImage = image
          selectedDoc.thumbImage = image.imageByScalingAndCroppingForSize(CGSize(width: 44, height: 44))
          reloadSelectedBugRow()
        }
      }
    }
    

    当这个方法被调用时,这意味着picture taker完成了它的工作。但是,用户也可以取消这个操作,并且你也不会得到任何图片。
    那么,你检查控件返回OK(NSModalResponseOK),并且你已经有了新的有效的图片。
    你现在应该已经熟悉了剩下的代码。首先,你得到选择的Bug,和table view选择的cell。这里唯一的区别是在这里,你更新model和cell的图片。
    编译并且运行app。如果你选择了一个bug并且选择Change Picture,你将会可以选择你电脑的图片,甚至通过电脑的摄像头捕捉新的图片。这个图片将会关联选择的bug。
    所以,现在记得应用程序差不多准备好了。你可以看到bug的详细信息,添加或者删除bugs,修改它们的名字、为它们评分、甚至修改他们的图片!!
    osx-tutorial-app2-5-angrybug1

    到哪里去?

    这里是你在这个系列教程中当前开发的所有代码的示例工程
    在教程的最后一部分,你将会学到如何发布应用程序,并且学习使一个应用程序看起来更好、更专业的小细节。
    敬请关注,如果你有任何问题,随时在下面的评论中加入讨论!