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
  1. 实现路径

    • 创建 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) 
                }
            }
        }
      
  2. 在 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
    }
  1. 缩放地图使路径更加合适地显示

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

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

  3. 展示 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-
![](https://o7vs282dx.qnssl.com/wxzf.JPG)
如果感觉此文对你有帮助,请随意打赏支持作者 😘

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!