Intermediate iOS Programming with Swift 笔记 Ⅰ

最近花了点时间,把 《Intermediate iOS Programming with Swift》 刷了一遍,这是第一部分

二、Adding Sections and Index List in UITableView

1. 给 tableView 增加索引

只需要增加 sectionIndexTitlesForTableView 方法,返回一个数组即可:

override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {  
    return animalSectionTitles
}

2. 应该添加从 A ~ Z 的索引:

let animalIndexTitles = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"]

override func sectionIndexTitlesForTableView(tableView: UITableView) -> [AnyObject]! {  
    return animalIndexTitles
}

3. 新旧索引映射

因为新提供的索引和实际存在索引之间存在差异,当新的索引被按下去的时候,你需要明确实现 sectionForSectionIndexTitle 方法告诉真实的 section number

override func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {  
// find 是全局函数,找到对应元素的索引
    if let index = find(animalSectionTitles, title) { 
        return index
    }
    return -1 
}

4. 自定义 SECTION HEADERS

更改 section header 高度

override func tableView(tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {  
    return 50
}

更改 section header 字体

override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {  
    let headerView = view as UITableViewHeaderFooterView
    headerView.textLabel.textColor = UIColor.orangeColor()
    headerView.textLabel.font = UIFont(name: "Avenir", size: 25.0)
}


三、ANIMATING TABLE VIEW CELL

1. 为 cell 创建一个简单的 Fade-in 动画

需要在 tableView(_:willDisplayCell:forRowAtIndexPath:):中去定义 cell 的动画:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {  
    // Define the initial state (Before the animation) 
    cell.alpha = 0

    // Define the final state (After the animation)
    UIView.animateWithDuration(1.0, animations: { cell.alpha = 1 }) 
}

2. 使用 CATransform3D 创建旋转动画

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    // Define the initial state (Before the animation) 角度是 π / 2
    let rotationAngleInRadians = 90.0 * CGFloat(M_PI/180.0);
    // 绕 Z 轴旋转
    let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, 0, 0, 1); 
    cell.layer.transform = rotationTransform;

    // Define the final state (After the animation) 
    //使用 CATransform3DIdentity 将cell 重置为初始位置
    UIView.animateWithDuration(1.0, animations: { cell.layer.transform = CATransform3DIdentity })
}

3. 使用 CATransform3D 创建飞入动画

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    // Define the initial state (Before the animation)
    let translateTransform = CATransform3DTranslate(CATransform3DIdentity, -500, 100, 0)
    cell.layer.transform = translateTransform

    // Define the final state (After the animation) 
    UIView.animateWithDuration(1.0, animations: { cell.layer.transform = CATransform3DIdentity })
}

4. 用 CATransform3DConcat 将上面两个动画结合起来

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {

    // Define the initial state (Before the animation)
    let rotationAngleInRadians = 90 * CGFloat(M_PI/180)
    let rotationTransform = CATransform3DMakeRotation(rotationAngleInRadians, 0, 0, 1)
    let translateTransform = CATransform3DTranslate(CATransform3DIdentity, -500, 100, 0)
    let transform = CATransform3DConcat(rotationTransform, translateTransform)
    cell.layer.transform = transform

    // Define the final state (After the animation) 
    UIView.animateWithDuration(3.0, animations: { () -> Void in
        cell.layer.transform = CATransform3DIdentity
    })

--

四、HOW TO READ AND PARSE JSON

本章创建一个 KivaLoan Demo,从 http://api.kivaws.org/v1/loans/newest.json 读取相应的信息显示在 tableview 上,主要记录 Loan(贷款信息)包括:Name, Country, Amount, Use。

在线解析 json :http://jsonviewer.stack.hu

1. 创建json data model

首先为 loan 创建一个 model,新建一个类命名为 Loan.swift

class Loan {  
    var name:String = "" 
    var country:String = "" 
    var use:String = ""
    var amount:Int = 0
}

2. 下载 json 数据

  1. 构造 NSURL 用 NSURL(string: kivaLoadURL)
  2. 创建 NSURLRequest
  3. NSURLSession 提供了三种方式:

    • NSURLSessionDataTask:从获取数据到内存
    • NSURLSessionDownloadTask:下载 file 到磁盘
    • NSURLSessionUploadTask:从磁盘上传数据

    这里使用 NSURLSessionDataTask ,且 NSURLSession API 是异步的,最后别忘了调用 task.resume()

    func getLatestLoans() {
        let request = NSURLRequest(URL: NSURL(string: kivaLoadURL)!) 
        let urlSession = NSURLSession.sharedSession()
        let task = urlSession.dataTaskWithRequest(request,completionHandler: { (data, response, error) -> Void in
            if error != nil { 
                println(error.localizedDescription)
            }
        // Parse JSON data
        self.loans = self.parseJsonData(data)
        // Reload table view dispatch_async(dispatch_get_main_queue(), {
            self.tableView.reloadData() })
        })
        task.resume() 
    }
    
  4. 解析 json 主要通过:

    let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options:
                            NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
    

    通常 json 被解析为一个 Dictionary 或 Array

    func parseJsonData(data: NSData) -> [Loan] {
        var resultLoans = [Loan]()
        var error: NSError?
        let jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: &error) as? NSDictionary
        if error != nil {
            println(error?.localizedDescription)
        }
        // Parse JSON data
        let jsonLoans = jsonResult?["loans"] as [AnyObject]
        for jsonLoan in jsonLoans {
            let loan = Loan()
            loan.name = jsonLoan["name"] as String
            loan.use = jsonLoan["use"] as String
            loan.amount = jsonLoan["loan_amount"] as Int
            let location = jsonLoan["location"] as [String:AnyObject]
            loan.country = location["country"] as String
            resultLoans.append(loan)
        }
        return resultLoans
    }
    


五、HOW TO INTEGRATE TWITTER AND FACEBOOK SHARING

1. 载入框架 import Social

2. 使用 SLComposeViewController 并配置

  • 先判断 SLComposeViewController.isAvailableForServiceType(...)
  • 然后再初始化:SLComposeViewController(forServiceType:...)
  • 最后配置
// Twitter
let twitterAction = UIAlertAction(title: "Twitter", style: UIAlertActionStyle.Default, handler: { (action) -> Void in  
    if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter) {
        let tweetComposer = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
        tweetComposer.setInitialText(self.restaurantNames[indexPath.row])
        tweetComposer.addImage(UIImage(named: self.restaurantImages[indexPath.row]))
        self.presentViewController(tweetComposer, animated: true, completion: nil) 
    } else {
        let alertMessage = UIAlertController(title: "Twitter Unavailable", message: "You haven't registered your Twitter account. Please go to Settings > Twitter to create one.", preferredStyle: .Alert)
        alertMessage.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        self.presentViewController(alertMessage, animated: true, completion: nil) }
})

//Facebook
let facebookAction = UIAlertAction(title: "Facebook", style: UIAlertActionStyle.Default, handler: { (action) -> Void in  
    if SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook) {
        let facebookComposer = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
        facebookComposer.setInitialText(self.restaurantNames[indexPath.row])
        facebookComposer.addImage(UIImage(named: self.restaurantImages[indexPath.row]))
        facebookComposer.addURL(NSURL(string: "http://www.appcoda.com"))
        self.presentViewController(facebookComposer, animated: true, completion: nil) 
    } else {
        let alertMessage = UIAlertController(title: "Facebook Unavailable", message: "You haven't registered your Facebook account. Please go to Settings > Facebook to 
            sign in or create one.", preferredStyle: .Alert)
        alertMessage.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
        self.presentViewController(alertMessage, animated: true, completion: nil) }
})


六、ADDING ATTACHMENT USING MESSAGEUI FRAMEWORK

1. 载入框架 import MessageUI

2. 判断是否能够发送 Email MFMailComposeViewController.canSendMail()

3. 创建 MFMailComposeViewController 实例let mailComposer = MFMailComposeViewController()

4. 配置 MFMailComposeViewController

mailComposer.setSubject(emailTitle)  
mailComposer.setMessageBody(messageBody, isHTML: false)  
mailComposer.setToRecipients(toRecipients)  

5. 创建附件

mailComposer.addAttachmentData(fileData, mimeType: mimeType, fileName: filename)  

带 3 个参数:fileData mimeType filename

  • 得到 filename 和 fileData:

    let fileparts = attachmentFile.componentsSeparatedByString(".") 
    let filename = fileparts[0]
    let fileExtension = fileparts[1]
    // Get the resource path and read the file using NSData
    let filePath = NSBundle.mainBundle().pathForResource(filename, ofType: fileExtension)
    let fileData = NSData(contentsOfFile: filePath!)
    
  • 得到 mimeType(MIME 描述消息内容类型的因特网标准,MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据):

    // Determine the MIME type var mimeType = ""
    switch fileExtension {
        case "jpg": mimeType = "image/jpeg"
        case "png": mimeType = "image/png"
        case "doc": mimeType = "application/msword"
        case "ppt": mimeType = "application/vnd.ms-powerpoint" case "html": mimeType = "text/html"
        case "pdf": mimeType = "application/pdf"
        default: mimeType = ""
    }
    
  • 创建附件

    mailComposer.addAttachmentData(fileData, mimeType: mimeType, fileName: filename)
    

6. 显示到 VC 上

presentViewController(mailComposer, animated: true, completion: nil) }  


七、SENDING SMS AND MMS

MessageUI framework 还支持 APP 内发送 SMS

1. 载入框架 import MessageUI

2. 声明遵守 MFMessageComposeViewControllerDelegate

这个代理主要操控下面几种情况:

  • 用户取消发送
  • 用户发送成功
  • 用户发送失败
func messageComposeViewController(controller: MFMessageComposeViewController!, didFinishWithResult result: MessageComposeResult) {  
        switch result.value {
        case MessageComposeResultCancelled.value:
            println("SMS Cancelled")
        case MessageComposeResultFailed.value:
            let messageAlert = UIAlertController(title: "Failure", message: "Failed to send the message.", preferredStyle: .Alert)
            messageAlert.addAction(UIAlertAction(title: "Ok", style: .Default, handler: nil))
            presentViewController(messageAlert, animated: true, completion: nil)
        case MessageComposeResultSent.value:
            println("SMS sent")
        default:
            break
        }
        dismissViewControllerAnimated(true, completion: nil)
    }

3. 创建设置 Message

  • 先判断能否发消息

  • MFMessageComposeViewController.canSendText()

  • 创建 let messageController = MFMessageComposeViewController()

  • 设置

    messageController.messageComposeDelegate = self
    messageController.recipients = ["12345678", "72345524"]
    messageController.body = "Just sent the \(attachment) to your email. Please check!"
    
  • 展示到 VC 上

  • presentViewController(messageController, animated: true, completion: nil)

4. 发送彩信(MMS)

  • 得到附件的 URL 地址:

    // Adding file attachment
    let fileparts = attachment.componentsSeparatedByString(".")
    let filename = fileparts[0]
    let fileExtension = fileparts[1]
    let filePath = NSBundle.mainBundle().pathForResource(filename, ofType: fileExtension) 
    let fileUrl = NSURL.fileURLWithPath(filePath!)
    
  • 添加附件到 SMS

    messageController.addAttachmentURL(fileUrl, withAlternateFilename: nil)
    

5. 如果不想在 APP 内部发送消息,跳转到系统短信用 URL scheme

系统内建的 URL scheme 支持 http, mailto, tel, 和 sms,具体实现如下:

UIApplication.sharedApplication().openURL(NSURL(string: "sms:123456789")!)  


八、HOW TO GET DIRECTION AND DRAW ROUTE IN MAP

MKDirections API 允许 iOS 开发者访问 Apple 服务器上的路径数据。MKDirections 可以根据你的需要返回乘车或者步行的详细路径。

1. 显示用户位置

为了显示路径,首先确定用户位置,通常情况下只需将 showsUserLocation 开启即可

mapView.showsUserLocation = YES  

但是上面的设置在 iOS 8 会失败,因为从 iOS 8 起需要明确地向用户请求权限:

  • 调用 locationManager.requestWhenInUseAuthorization()locationManager.requestAlwaysAuthorization() 申请权限

    let locationManager = CLLocationManager()
    
    
    / Request for a user's authorization for location services
    locationManager.requestWhenInUseAuthorization()
    
    
    let status = CLLocationManager.authorizationStatus()
    if status == CLAuthorizationStatus.AuthorizedWhenInUse {
        self.mapView.showsUserLocation = true 
    }
    
  • 添加 key (NSLocationWhenInUseUsageDescription / NSLocationAlwaysUsageDescription)到 Info.plist 中

2. 使用 MKDIRECTIONS API 得到 Route 信息

现在计算用户位置到餐馆之间的距离:

  1. 首先定义 当前选中餐馆的位置:

    var currentPlacemark:CLPlacemark?
    

    然后将 geoCoder.geocodeAddressString 转换的结果 placemark 赋给我们当前餐馆:

    self.currentPlacemark = placemark
    
  2. 实现路径

    • 创建 MKDirectionsRequest() 请求
    • 配置 directionRequest
    • 调用 calculateDirectionsWithCompletionHandler(_completionHandler: MKDirectionsHandler!)计算路径,该步骤是异步请求的,结果在 completionHandler 中返回

      @IBAction func showDirection(sender: AnyObject) { 
          let directionRequest = MKDirectionsRequest()        
          // Set the source and destination of the route
          // directionRequest 需要的是 MKPlacemark,而 MKPlacemark 是 CLPlacemark 的子类
          directionRequest.setSource(MKMapItem.mapItemForCurrentLocation())
          let destinationPlacemark = MKPlacemark(placemark: currentPlacemark)
          directionRequest.setDestination(MKMapItem(placemark: destinationPlacemark)) 
          directionRequest.transportType = MKDirectionsTransportType.Automobile
          // Calculate the direction
          let directions = MKDirections(request: directionRequest)
          directions.calculateDirectionsWithCompletionHandler { (routeResponse, routeError) -> Void in
              if routeError != nil {
                  println("Error: \(routeError.localizedDescription)")
              } else {
                  let route = routeResponse.routes[0] as MKRoute
                  self.mapView.addOverlay(route.polyline, level: MKOverlayLevel.AboveRoads) 
              }
          }
      }
      
  3. 在 MKMapViewDelegate 方法中画出路径,在该方法中主要用到了MKPolylineRenderer 在文档中的解释是:

    The MKPolylineRenderer class provides the visual representation for an MKPolyline overlay object

    func mapView(mapView: MKMapView!, rendererForOverlay overlay: MKOverlay!) -> MKOverlayRenderer! { 
        let renderer = MKPolylineRenderer(overlay: overlay)
        renderer.strokeColor = UIColor.blueColor()
        renderer.lineWidth = 3.0
        return renderer
    }
    
  4. 缩放地图使路径更加合适地显示

    首先得到 route 的大概轮廓,然后添加到 mapView 中

    let rect = route.polyline.boundingMapRect
    self.mapView.setRegion(MKCoordinateRegionForMapRect(rect), animated: true)
    
  5. 使用 SEGMENTED CONTROL 切换 MKDirectionsTransportType 类型,这里主要是切换 Automobile 和 Walking

  6. 展示 Route steps,我们知道 MKRoute 类的 steps 属性就是包含了 MKRouteStep 对象的数组,而且 MKRouteStep 对象有一个 instructions 属性描述了详细路径向导信息,具体实现如下:

    • 添加一个带 navigation controller 的 table view controller
    • 设置 var routeSteps = [MKRouteStep]() 并将其设置为数据源
    • 回到 MapViewController.swift,增加 var currentRoute:MKRoute?,并为餐馆annotationView 增加一个DetailDisclosure按钮

      annotationView.rightCalloutAccessoryView =
      UIButton.buttonWithType(UIButtonType.DetailDisclosure) as! UIView
      
    • 监听这个按钮被按下同样是用到 MKMapViewDelegate 的 calloutAccessoryControlTapped:

      func mapView(mapView: MKMapView!, annotationView view: MKAnnotationView!, calloutAccessoryControlTapped control: UIControl!) {
             self.performSegueWithIdentifier("showSteps", sender: view)
      }
      
    • prepareForSegue 中将 MapViewController 的 currentRoute 中的 steps 传递给 routeTableViewController.routeSteps


九、SEARCH NEARBY POINTS OF INTEREST USING LOCAL SEARCH

从 iOS 7 开始,新的 Search API 也就是 MKLocalSearch 允许 开发者搜索地图上感兴趣的点并且显示到屏幕上。MKLocalSearch 的用法和前面的 MKLocalSearchRequest 非常类似,你可以将 map 的显示区域缩小到搜索结果的范围内,而搜索过程也是异步的。

1. 创建 searchRequest,并配置

let searchRequest = MKLocalSearchRequest()  
searchRequest.naturalLanguageQuery = restaurant.category  
searchRequest.region = mapView.region  

2. 调用 startWithCompletionHandler 开始搜索,并对闭包中的返回值 response 进行处理

let localSearch = MKLocalSearch(request: searchRequest) localSearch.startWithCompletionHandler { (response, error) -> Void in  
    if error != nil {
        println("Error: \(error.localizedDescription)") 
        return
    }
    // MKMapItem 封装了地图上具体点的信息
    let mapItems = response.mapItems as [MKMapItem]
    // MKAnnotation 提供了注释信息 
    var nearbyAnnotations:[MKAnnotation] = []
    if mapItems.count > 0 {
        for item in mapItems {
            // Add annotation
            let annotation = MKPointAnnotation()
            annotation.title = item.name
            annotation.subtitle = item.phoneNumber 
            annotation.coordinate = item.placemark.location.coordinate          nearbyAnnotations.append(annotation)
        } 
    }
    // 显示
    self.mapView.showAnnotations(nearbyAnnotations, animated: true) 
}


十、AUDIO RECORDING AND PLAYBACK

1. AVFoundation Framework

AV Foundation Framework 提供了播放录制音频的功能,具体由以下两个类负责:

  • AVAudioPlayer 播放音频
  • AVAudioRecorder 录制音频

2. 使用 AVAudioRecorder 录制音频

  1. import AVFoundation,并遵守 AVAudioRecorderDelegate
  2. 声明 AVAudioRecorderAVAudioPlayer 的实例:

    var audioRecorder:AVAudioRecorder? 
    var audioPlayer:AVAudioPlayer?
    
  3. AVAudioRecorder 的一些准备工作,并不会立即开始,只有调用 record 方法才会开始录音

    • 设置 audio file 路径

      var directoryURL = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: 
      NSSearchPathDomainMask.UserDomainMask).first as NSURL
      var audioFileURL = directoryURL.URLByAppendingPathComponent("MyAudioMemo.m4a")
      
    • 设置 audio session(单例)

      let audioSession = AVAudioSession.sharedInstance()
      audioSession.setCategory(AVAudioSessionCategoryPlayAndRecord, withOptions: AVAudioSessionCategoryOptions.DefaultToSpeaker, error: nil)
      
    • 定义 recorder setting

      var recorderSetting = [AVFormatIDKey:kAudioFormatMPEG4AAC,
                       AVSampleRateKey: 44100.0,
                 AVNumberOfChannelsKey: 2 ]
      
    • 初始化并准备 recorder

      audioRecorder = AVAudioRecorder(URL: audioFileURL, settings: recorderSetting, error: nil) 
      audioRecorder?.delegate = self
      audioRecorder?.meteringEnabled = true
      audioRecorder?.prepareToRecord()
      
  4. 开始录音

    • 先激活 AVAudioSession 再录音

      let audioSession = AVAudioSession.sharedInstance()
      audioSession.setActive(true, error: nil)
      
    • 录音 recorder.record()

  5. 停止录音

    • 先停止录音 audioRecorder?.stop()
    • 再关闭 AVAudioSession

      let audioSession = AVAudioSession.sharedInstance()
      audioSession.setActive(false, error: nil)
      
  6. 实现 AVAudioRecorderDelegate 方法,录音结束时调用 audioRecorderDidFinishRecording 方法

3. 使用 AVAudioPlayer 播放音频

  1. 初始化 audio player,然后分配一个 sound file 给它 AVAudioPlayer(contentsOfURL: recorder.url, error: nil)
  2. 制定 audio player 的 delegate audioPlayer?.delegate = self
  3. 调用 play 方法播放音频 audioPlayer?.play()
  4. 实现 AVAudioPlayerDelegate 方法,播放完毕时调用 audioPlayerDidFinishPlaying

-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!