2015年3月13日 星期五

[swift] CoreData (2) 新增,刪除CoreData資料


這邊做個簡單的介面,讓TableView的資料來自資料庫,並能把資料存到資料庫裡面

  • 首先先把TableView的資料來源設定成來自資料庫,完整程式碼如下,這樣就已經把Table的datasource friends來源改成SQLite了
            
    import UIKit
    import CoreData
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, NSFetchedResultsControllerDelegate{
        @IBOutlet weak var tableview: UITableView!
        var friends : [Friend] = []
        var fetchRequestController:NSFetchedResultsController!
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //讀取Friend的Entity
            var fetchRequest = NSFetchRequest(entityName: "Friend")
            //排序方式用name這個欄位
            let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
                //delegate設定為自己時,當新增修改刪除SQLite的資料時會呼叫以下事件
                //controllerWillChangeContent()
                //controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
                //controllerDidChangeContent(_:)
                fetchRequestController.delegate = self
                var e:NSError?
                var result = fetchRequestController.performFetch(&e)
                friends = fetchRequestController.fetchedObjects as [Friend]
                
                if result != true{
                     println(e?.localizedDescription)
                }
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let identifier = "cell"
            var cell = tableView.dequeueReusableCellWithIdentifier(identifier) as customCell
            cell.nameLabel.text = friends[indexPath.row].name
            cell.telLabel.text = friends[indexPath.row].tel.stringValue
            cell.picImageView.image = UIImage(data: friends[indexPath.row].picture)
            cell.marryLabel.text = friends[indexPath.row].marry.boolValue ? "YES" : "NO"
            return cell
        }
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return friends.count
        }
    }
    
  • 接著寫按下save後的事件將檔案能寫到資料庫中,在這之前先處理Yes and no的按鈕和載入圖片按鈕
    Yes and No按鈕
            
        @IBOutlet weak var YesButton: UIButton!
        @IBOutlet weak var NoButton: UIButton!
    
        @IBAction func MarryButtonClick(sender: AnyObject) {
            YesButton.backgroundColor = (sender as UIButton == YesButton) ? UIColor.redColor() : UIColor.lightGrayColor()
            NoButton.backgroundColor = (sender as UIButton == NoButton) ? UIColor.redColor() : UIColor.lightGrayColor()
        }
    
    

    實作載入圖片的按鈕,需要讓viewController實作UIImagePickerControllerDelegate和UINavigationControllerDelegate
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, NSFetchedResultsControllerDelegate , UIImagePickerControllerDelegate,UINavigationControllerDelegate
    {
    
    }
    
    selectPicture的按鈕
        @IBAction func SelectPictureClick(sender: AnyObject) {
            let imagePicker = UIImagePickerController()
            imagePicker.allowsEditing = false
            imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
            imagePicker.delegate = self
            //顯示挑選圖片的視窗
            self.presentViewController(imagePicker, animated: true, completion: nil)
        }
        
        func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
            PicImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
            PicImageView.contentMode = UIViewContentMode.ScaleAspectFill
            PicImageView.clipsToBounds = true
            
            //關閉挑選圖片的視窗
            dismissViewControllerAnimated(true, completion: nil)
        }
    
    
    接著執行看看應該結果
  • 接著把Save按鈕按下後存到SQLite的事件寫好
        @IBAction func SaveClick(sender: AnyObject) {
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                var friend = NSEntityDescription.insertNewObjectForEntityForName("Friend", inManagedObjectContext: manageObjectContext) as Friend
                friend.name = NameTextField.text
                friend.tel = TelTextField.text.toInt()
                friend.marry = (YesButton.backgroundColor == UIColor.redColor()) ? true : false
                friend.picture = UIImagePNGRepresentation(PicImageView.image)
                
                var e:NSError?
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                    return
                }
            }
        }
    
    接著執行看看會發現按了儲存按鈕後雖然存進資料庫了,但沒有讓TableView馬上連動顯示,必須重新開APP才會顯示在table上
  • 還記得之前有繼承NSFetchedResultsControllerDelegate嗎?這時候派上用場了,補上幾個事件讓他可以跟TableView連動吧
           
        //資料庫準備更新了
        func controllerWillChangeContent(controller: NSFetchedResultsController) {
            tableview.beginUpdates()
        }
        
        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
            switch type{
            case .Insert:
                tableview.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete:
                tableview.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update:
                tableview.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            default:
                tableview.reloadData()
            }
            
            friends = controller.fetchedObjects as [Friend]
        }
        
        func controllerDidChangeContent(controller: NSFetchedResultsController) {
            tableview.endUpdates()
        }
    
    
    接著再執行看看應該就可以看到他會馬上顯示了
  • 最後來個刪除吧!!這邊只舉例刪除該如何做,就不加到例子當中了
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                //簡單說就是挑出那個想刪除的物件
                var friendToDelete = self.fetchRequestController.objectAtIndexPath(NSIndexPath(index: 0)) as Friend
                //呼叫刪除方法把它丟進去
                manageObjectContext.deleteObject(friendToDelete)
                var e:NSError?
                //儲存
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                }
            }
    
    
  • 基本上有操作過.NET Entity物件的話,這邊的概念幾乎都雷同,最後補上完整的程式碼
    import UIKit
    import CoreData
    class ViewController: UIViewController , UITableViewDelegate , UITableViewDataSource, UINavigationControllerDelegate ,NSFetchedResultsControllerDelegate , UIImagePickerControllerDelegate{
        @IBOutlet weak var tableview: UITableView!
        var friends : [Friend] = []
        var fetchRequestController:NSFetchedResultsController!
        
        @IBOutlet weak var NameTextField: UITextField!
        @IBOutlet weak var TelTextField: UITextField!
        @IBOutlet weak var PicImageView: UIImageView!
        @IBOutlet weak var YesButton: UIButton!
        @IBOutlet weak var NoButton: UIButton!
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //讀取Friend的Entity
            var fetchRequest = NSFetchRequest(entityName: "Friend")
            //排序方式用name這個欄位
            let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
            if let managedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                fetchRequestController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
                //delegate設定為自己時,當新增修改刪除SQLite的資料時會呼叫以下事件
                //controllerWillChangeContent()
                //controller(_:didChangeObject:atIndexPath:forChangeType:newIndexPath:)
                //controllerDidChangeContent(_:)
                fetchRequestController.delegate = self
                var e:NSError?
                var result = fetchRequestController.performFetch(&e)
                friends = fetchRequestController.fetchedObjects as [Friend]
                
                if result != true{
                     println(e?.localizedDescription)
                }
            }
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let identifier = "cell"
            var cell = tableView.dequeueReusableCellWithIdentifier(identifier) as customCell
            cell.nameLabel.text = friends[indexPath.row].name
            cell.telLabel.text = friends[indexPath.row].tel.stringValue
            cell.picImageView.image = UIImage(data: friends[indexPath.row].picture)
            cell.marryLabel.text = friends[indexPath.row].marry.boolValue ? "YES" : "NO"
            return cell
        }
    
        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return friends.count
        }
        
        @IBAction func MarryButtonClick(sender: AnyObject) {
            YesButton.backgroundColor = (sender as UIButton == YesButton) ? UIColor.redColor() : UIColor.lightGrayColor()
            NoButton.backgroundColor = (sender as UIButton == NoButton) ? UIColor.redColor() : UIColor.lightGrayColor()
        }
        
        @IBAction func SelectPictureClick(sender: AnyObject) {
            let imagePicker = UIImagePickerController()
            imagePicker.allowsEditing = false
            imagePicker.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
            imagePicker.delegate = self
            //顯示挑選圖片的視窗
            self.presentViewController(imagePicker, animated: true, completion: nil)
        }
        
        func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
            PicImageView.image = info[UIImagePickerControllerOriginalImage] as? UIImage
            PicImageView.contentMode = UIViewContentMode.ScaleAspectFill
            PicImageView.clipsToBounds = true
            
            //關閉挑選圖片的視窗
            dismissViewControllerAnimated(true, completion: nil)
        }
        
        @IBAction func SaveClick(sender: AnyObject) {
            if let manageObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext{
                var friend = NSEntityDescription.insertNewObjectForEntityForName("Friend", inManagedObjectContext: manageObjectContext) as Friend
                friend.name = NameTextField.text
                friend.tel = TelTextField.text.toInt()
                friend.marry = (YesButton.backgroundColor == UIColor.redColor()) ? true : false
                friend.picture = UIImagePNGRepresentation(PicImageView.image)
                
                var e:NSError?
                if manageObjectContext.save(&e) != true{
                    println("error: \(e?.localizedDescription)")
                    return
                }
            }
        }
        
        //資料庫準備更新了
        func controllerWillChangeContent(controller: NSFetchedResultsController) {
            tableview.beginUpdates()
        }
        
        func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
            switch type{
            case .Insert:
                tableview.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete:
                tableview.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update:
                tableview.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            default:
                tableview.reloadData()
            }
            
            friends = controller.fetchedObjects as [Friend]
        }
        
        func controllerDidChangeContent(controller: NSFetchedResultsController) {
            tableview.endUpdates()
        }
    }
    
    
    

2015年3月12日 星期四

[swift] CoreData (1) 建立CoreData


CoreData是是一種以物件導向方式讓開發者與跟資料庫互動的框架,好處是你不需要懂SQL也能對SQLite做操作


  • 接著來筆記如何實作CoreData,首先先建立一個專案並把Use Core Data勾選起來
  • 接著打開AppDelegate最下面可以看到多了一大堆程式碼
    // MARK: - Core Data stack
    
        lazy var applicationDocumentsDirectory: NSURL = {
            // The directory the application uses to store the Core Data store file. This code uses a directory named "tw.com.xxx.Coredata" in the application's documents Application Support directory.
            let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
            return urls[urls.count-1] as NSURL
        }()
    
        lazy var managedObjectModel: NSManagedObjectModel = {
            // The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
            let modelURL = NSBundle.mainBundle().URLForResource("Coredata", withExtension: "momd")!
            return NSManagedObjectModel(contentsOfURL: modelURL)!
        }()
    
        lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
            // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
            // Create the coordinator and store
            var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
            let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("Coredata.sqlite")
            var error: NSError? = nil
            var failureReason = "There was an error creating or loading the application's saved data."
            if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
                coordinator = nil
                // Report any error we got.
                let dict = NSMutableDictionary()
                dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
                dict[NSLocalizedFailureReasonErrorKey] = failureReason
                dict[NSUnderlyingErrorKey] = error
                error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
                // Replace this with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                NSLog("Unresolved error \(error), \(error!.userInfo)")
                abort()
            }
            
            return coordinator
        }()
    
        lazy var managedObjectContext: NSManagedObjectContext? = {
            // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
            let coordinator = self.persistentStoreCoordinator
            if coordinator == nil {
                return nil
            }
            var managedObjectContext = NSManagedObjectContext()
            managedObjectContext.persistentStoreCoordinator = coordinator
            return managedObjectContext
        }()
    
        // MARK: - Core Data Saving support
    
        func saveContext () {
            if let moc = self.managedObjectContext {
                var error: NSError? = nil
                if moc.hasChanges && !moc.save(&error) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    NSLog("Unresolved error \(error), \(error!.userInfo)")
                    abort()
                }
            }
        }
    
  • 接著建立物件託管模型
  • 接著建立Entities

    圖片需要用Binary Data來儲存
  • 接著建立Class來對應Entity
    import Foundation
    import CoreData
    class Friend:NSManagedObject  {
        @NSManaged var name:String!
        @NSManaged var tel:NSNumber!
        @NSManaged var picture:NSData!
        @NSManaged var marry:NSNumber!
    }
    
    *對應Entity中的Boolean, Integer, Float and Double attributes,都是用NSNumber
  • 接著設定Entity與Class的對應關係,如圖:

    這樣託管物件的建立就已經完成了,下一篇介紹如何叫用

2015年3月11日 星期三

[swift] IOS地圖運用 (6) 透過CLGeocoder轉換經緯度與地址


IOS提供CLGeocoder將地址轉成經緯度,或是把經緯度轉換成地址,詳細語法如下

  • 記得先Import MapKit
  • 地址轉經緯度(geocodeAddressString)
    let geoCoder = CLGeocoder()
    geoCoder.geocodeAddressString("這邊帶入地址", completionHandler: {
       (placemarks:[AnyObject]!,error:NSError!) -> Void in
       if error != nil{
          println(error)
          return
       }
                
       if placemarks != nil && placemarks.count > 0{
          let placemark = placemarks[0] as CLPlacemark
          //placemark.location.coordinate 取得經緯度的參數            
       }
    })
    
  • 經緯度轉地址的方法
    let geoCoder = CLGeocoder()
    geoCoder.reverseGeocodeLocation(CLLocation(latitude: 25.024839 , longitude: 121.549170), completionHandler: {
       (placemarks:[AnyObject]!,error:NSError!) -> Void in
       if error != nil{
          println(error)
          return
       }
    
       //name         街道地址
       //country      國家
       //province     省
       //locality     市
       //sublocality  縣.區
       //route        街道、路
       //streetNumber 門牌號碼
       //postalCode   郵遞區號
       if placemarks != nil && placemarks.count > 0{
          let placemark = placemarks[0] as CLPlacemark
          //這邊拼湊轉回來的地址
          //placemark.name
       }
    })
    

[swift] 製作動畫


目標結果如圖


  • 首先先把最後所有按鈕要放的位置擺好
  • 把每個按鈕跟ViewController做對應
        @IBOutlet weak var backgroundImageView: UIImageView!
        @IBOutlet weak var fbButton: UIButton!
        @IBOutlet weak var twitterButton: UIButton!
        @IBOutlet weak var messageButton: UIButton!
        @IBOutlet weak var mailButton: UIButton!
    
  • 接著用CGAffineTransformMakeTranslation的方式將所有按鈕移到動畫的起始位置,
    這邊解釋一下程式中那些數字怎麼決定的,CGAffineTransformMakeTranslation需要給定x y值,如果(0, -300)表示x軸不動,Y軸比原本你在StoryBoard放的位置還要往上移300個點的位置,反之(0, 300)則表示比原本擺放的位置往下移動300點
        
        override func viewDidLoad() {
            super.viewDidLoad()
            self.fbButton.transform = CGAffineTransformMakeTranslation(0, -300)
            self.messageButton.transform = CGAffineTransformMakeTranslation(0, -364)
            
            self.mailButton.transform = CGAffineTransformMakeTranslation(0, 300)
            self.twitterButton.transform = CGAffineTransformMakeTranslation(0, 364)
        }
    
  • 接著大家測試一下程式,應該會發現東西的確是跑出螢幕了,接著在viewDidAppear補上以下程式碼就大功告成了!!
        override func viewDidAppear(animated: Bool) {
            super.viewDidAppear(animated)
    
            //動畫,分成兩個區塊是因為有快有慢,差別在於delay多久再發動,usingSpringWithDamping是彈跳的反作用力,越小越劇烈
            UIView.animateWithDuration(0.9, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: nil, animations: {
                //跑回原來的點
                let translate = CGAffineTransformMakeTranslation(0, 0)
                self.messageButton.transform = translate
                self.twitterButton.transform = translate
            }, completion: nil)
            
            UIView.animateWithDuration(0.9, delay: 0.1, usingSpringWithDamping: 0.5, initialSpringVelocity: 0.5, options: nil, animations: {
                let translate = CGAffineTransformMakeTranslation(0, 0)
                self.fbButton.transform = translate
                self.mailButton.transform = translate
            }, completion: nil)
        }
    

2015年3月10日 星期二

[swift] 將圖片做出霧化效果


以下範例是參考做成的筆記:養成iOS8 App程式設計實力的25堂課:最新Swift開發教學

  • IOS8有提供將圖片霧化的效果,如果我們將一張圖片擺進都不調整的情況下看到的結果如下
  • 如果我們想把它變成這樣呢?
  • 程式碼撰寫如下:
        @IBOutlet weak var backgroundImageView: UIImageView!//背景圖   
        override func viewDidLoad() {
            super.viewDidLoad()
            //.Dark等於圖1
            //.ExtraLight等於圖2
            //.Light等於圖3
            var blurEffect = UIBlurEffect(style: UIBlurEffectStyle.Light)
            var blurEffectView = UIVisualEffectView(effect: blurEffect)
            blurEffectView.frame = view.bounds
            backgroundImageView.addSubview(blurEffectView)
        }
    

2015年3月9日 星期一

[swift] 製作IOS Table (5) UITableViewRowAction


開始之前想推薦這本書:養成iOS8 App程式設計實力的25堂課:最新Swift開發教學,只能說看了對於初學者的我來說受益良多,而且對於IOS很多觀念也更加清楚了

  • 這邊要實作的功能如下圖
  • IOS8的TableView當中,有個可以往左滑可以選擇更多動作的功能,Apple官方有提供相對應的事件可以實作它
  • 首先先Override TableView commitEditingStyle這個事件,什麼都先不要做就執行看看
    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
            
    }
    
    立馬預設的Delete按鈕就跑出來了
  • 接著補上以下code,再實際執行看看就可以發現該列可以刪除了
       func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
            if editingStyle == UITableViewCellEditingStyle.Delete{
                //將TableView的DataSource刪除
                self.restaurantArray.removeAtIndex(indexPath.row)
                //將TableView該列刪除
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        }
    
  • 但如果要客製更多按鈕呢?接下來改寫 editActionsForRowAtIndexPath這個事件,但需要特別注意的是如果我們新增了editActionsForRowAtIndexPath這個事件,則IOS就不會預設幫我們增加Delete的按鈕,所以Delete的按鈕也得自己做瞜~

    先把commitEditingStyle恢復成空值,只讓IOS知道我們要左滑出現功能列就好
       func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
          //把剛剛寫的都刪光光
       }
    
    接著改寫editActionsForRowAtIndexPath
        func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [AnyObject]? {
            var shareAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Share", handler: nil)
            
            //delete按鈕自己做   
            var deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Delete", handler: {
                (action:UITableViewRowAction! , indexPath:NSIndexPath!) -> Void in
                self.restaurantArray.removeAtIndex(indexPath.row)
                self.restaurantTable.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            })
    
            return [deleteAction,shareAction]
        }
    
    執行看看就可以看到多出了一過按鈕了,如果想改變按鈕的顏色可以這樣寫
    shareAction.backgroundColor = UIColor(red: 255/255, green: 166/255, blue: 51/255, alpha: 1)
    
  • 最後就可以得出圖1的結果啦!!!!

[swift] UIAlertController


  • APP常常有一種狀況是當使用者按下某個選項時,從下面滑出更多的選項讓它做進一步動作,例如在iphone的照片點選垃圾桶時

  • 在IOS 8中多了一個UIAlertController的類別來取代UIAlerView,實際作法如下
    首先將程式碼加到viewDidAppear,否則無法顯示效果,原因請看IOS生命週期的說明點我
        override func viewDidAppear(animated: Bool) {
            //建立UIAlertController
            let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .ActionSheet);
            
            //新增選項
            let callaction = UIAlertAction(title: "call xxx", style: .Default , handler:nil);
            
            //把選項加到UIAlertController
            quetion.addAction(callaction);
            //Show
            self.presentViewController(quetion, animated: true, completion: nil);
        }
    
    
    執行看到的結果如下圖
  • 如果將ActionSheet改成Alert,則結果
    let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .Alert);
    
  • 如果更進階一些,在選擇選項後有些回應時
    我們拉一個label到ViewController裡面來,並且在按下call xxx選項時,把label的字樣給改變
    class AlertViewController: UIViewController {
        @IBOutlet weak var showaction: UILabel!
        override func viewDidLoad() {
            super.viewDidLoad();
            
        }
        
        override func viewDidAppear(animated: Bool) {
            let quetion = UIAlertController(title: nil, message: "What Do u want?", preferredStyle: .Alert);
            
            
            let callaction = UIAlertAction(title: "call xxx",style: .Default, handler:{
                (action:UIAlertAction!) -> Void in
                self.showaction.text = "Do call xxx";
            });
            
            quetion.addAction(callaction);
            self.presentViewController(quetion, animated: true, completion: nil);
        }
    }
    
    執行看看吧!!

2015年3月8日 星期日

[swift] String To MD5


  • 我將這個Function寫在extension裡面,首先Swift要能用MD5的function必須先建立header檔並掛上(header檔如何建立請看這篇)
    #import <CommonCrypto/CommonCrypto.h>
    
  • 然後
        
    func md5() -> String! {
            let str = self.cStringUsingEncoding(NSUTF8StringEncoding)
            let strLen = CUnsignedInt(self.lengthOfBytesUsingEncoding(NSUTF8StringEncoding))
            let digestLen = Int(CC_MD5_DIGEST_LENGTH)
            let result = UnsafeMutablePointer<CUnsignedChar>.alloc(digestLen)
            
            CC_MD5(str!, strLen, result)
            
            var hash = NSMutableString()
            for i in 0..<digestLen {
                hash.appendFormat("%02x", result[i])
            }
            
            result.destroy()
            
            return String(format: hash) 
    }       
    

[swift] Extension


Swift這個語法常常會遇到參數型態間的轉換,寫慣了C#,常常遇到什麼參數轉換就會想要.toString() .toInt()阿之類的,偏偏Swift沒有這些function不打緊,還每種型態的變數互轉常常方式不一樣....搞得我每次遇到都要查一下到底這次又該用哪個function來轉換好,最後索性就把這些整理起來做成Extension,以後就跟C#一樣to啥to啥的就好.


  • 那接下來就來介紹怎麼擴充Swift的function語法
  • extension是關鍵字,擴充String類別型態的function
    extension String{
        func toDoublue() -> Double{
            return (self as NSString).doubleValue;
        }
    }
    
  • 結果

2015年3月6日 星期五

[SQL] 2008 R2 繁體中文版安裝問題


如果你作業系統是從英文版安裝與語系版本成為中文版的話,安裝SQL繁體中文版應該會遇到不明的錯誤而停止,上網找了一下資料發現這篇:
解決 Windows 7 線上更新語言包後安裝 SQL Server 2008 R2 繁體中文版失敗的問題

原因是從英文版透過語系包轉成的中文版的作業系統,他是香港繁體中文版,在安裝SQL時預設會去抓3076_ZHH_LP這個資料夾,但繁體中文版的SQL Server的資料夾為1028_CHT_LP。

所以解決方法就是,把全部的光碟內容複製下來,並且將1028_CHT_LP複製一份並且改名為3076_ZHH_LP,用這個資料夾安裝就可以順利完成了

2015年3月5日 星期四

[swift] NetWork (4) 解析Json


  • 先做一個json檔案來模擬接到時怎麼解析
    {
    "組織名稱":"普攏拱團隊",
    "組織成員":[
        {"姓名":"科科人","年齡":11},
        {"姓名":"可可狗","年齡":120}
    ]
    }
    
  • 在viewDidLoad加上,測試看看!!
            //將檔案讀出來
            let path = NSBundle.mainBundle().pathForResource("json", ofType: "txt");
            var data:NSData = NSData(contentsOfFile: path!)!;
            
            //解析Json
            var jsonobj = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as NSDictionary;
            println(jsonobj["組織名稱"] as String);
            println("組織成員");
            var arr = jsonobj["組織成員"] as NSArray;
            for i in (0...arr.count - 1){
                let mdic = arr[i] as NSDictionary;
                println(mdic["姓名"] as String);
                println(mdic["年齡"] as Int);
            }
    
    

2015年3月4日 星期三

[swift] NetWork (3) 判斷手機是否有網路連線可用


主要參考兩篇文章做修改:
Detect Internet status programmatically using Swift and Object-C language
[iOS] 即時判斷網路連線狀態 (Detect Network Status on Real-Time)


  • Apple已經用Objective-C寫了判斷網路的套件給大家使用,所以接下來會解說如何用Swift專案載入Objective-C套件
  • 先到這邊下載Reachability
  • 接著把Reachability專案中的Reachability.m和Reachability.h抓到專案中
  • 在專案中新增Header檔

  • 打開Header檔,把Reachability.h掛進去,這樣以後專案中引用Reachability裡面的class都不再Import了
  • 把Header檔加到專案的Build Setting中
  • 打開Reachability.m找到(void)dealloc改成
    - (void)dealloc
    {
        [self stopNotifier];
        if (_reachabilityRef != NULL)
        {
     CFRelease(_reachabilityRef);
        }
        [super dealloc];
    }
    
  • 到專案中的Build Phases,把Reachability.m加上-fno-objc-arc
  • 以上就全部掛載完成了,接著來看看這個套件如何使用,並讓它告知我們網路狀態
    打開view controller,新增一個監測網路的變數,並且在viewDidLoad寫下
        var internetReachability:Reachability!; //網路狀態監控
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //在通知中心註冊事件,當網路狀態有變動的時候會觸發
            NSNotificationCenter.defaultCenter().addObserverForName(kReachabilityChangedNotification, object: nil, queue: NSOperationQueue.mainQueue()) { (NSNotification) -> Void in
                let networksStatus: NetworkStatus = self.internetReachability.currentReachabilityStatus()
                var status: String!
                if networksStatus.value == 0 {
                    status = "Disconnection"
                } else if networksStatus.value == 1 {
                    status = "Connection"
                } else {
                    status = "Connection"
                }
                UIAlertView(title: "網路狀態", message: status, delegate: nil, cancelButtonTitle: "OK").show();
            }
            
            self.internetReachability = Reachability.reachabilityForInternetConnection();
            //開始監控狀況
            self.internetReachability.startNotifier()
       }
    
    在Mac上面執行看看,測試方法就是把mac的wifi連線給關閉,就會看到App跳出提示訊息了
  • 接下來把viewDidLoad改成如下,這段是告訴你目前使用的網路是什麼?Wifi or 3G之類的
    override func viewDidLoad() {
            super.viewDidLoad()
                  
            let statusReach: Reachability = Reachability.reachabilityForInternetConnection()
            let networksStatus: NetworkStatus = statusReach.currentReachabilityStatus()
            var status: String!
            if networksStatus.value == 0 {
                status = "NoReachable"
            } else if networksStatus.value == 1 {
                status = "ReachableViaWiFi"
            } else {
                status = "ReachableViaWWAN"
            }
            
            UIAlertView(title: "網路狀態", message: status, delegate: nil, cancelButtonTitle: "OK").show();
        }
    
    執行看看

2015年3月3日 星期二

[swift] NetWork (2) 非同步方式取得API資訊


為了避免撈取API資料太久而讓整個APP卡住,所以建議撈取API資料時都另外開出一個queue來非同步執行
var url:NSURL = NSURL(string: "http://xxx.com.tw/api/getdata")!;
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10);
request.HTTPMethod = "POST";
request.HTTPBody = "yourdata".dataUsingEncoding(NSUTF8StringEncoding);

//新增一個queue來執行API,避免API撈取資料太久把整個APP卡住
let queue :NSOperationQueue = NSOperationQueue();
//非同步方式取得資料
NSURLConnection.sendAsynchronousRequest(request, queue: queue, completionHandler:{ (response: NSURLResponse!, data: NSData!, error: NSError!) -> Void in
   var err:String? = error?.description;
   if(data != nil  && error == nil)
   {
      let responseString = NSString(data: data, encoding: NSUTF8StringEncoding);
      println("responseString = \(responseString)");
   }
});

[swift] NetWork (1) 以Get 和 Post方式傳送資料


用Post方式傳遞資料
        
        var url:NSURL = NSURL(string: "http://xxx.com.tw/api/postdata")!;
        let request:NSMutableURLRequest = NSMutableURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10);
        request.HTTPMethod = "POST";
        request.HTTPBody = "yourPostData".dataUsingEncoding(NSUTF8StringEncoding);
        
        let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
            data, response, error in
            
            if error != nil {
                println("error=\(error)")
                return
            }
            
            println("response = \(response)")
            
            //將收到的資料轉成字串print出來看看
            let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
            println("responseString = \(responseString)")
        }
        task.resume();


用Get方式傳遞資料
var url:NSURL = NSURL(string: "http://xxx.com.tw/api/getdata?id=5008&id=5009")!;
let request:NSURLRequest = NSURLRequest(URL: url, cachePolicy: NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval: 10);

let task = NSURLSession.sharedSession().dataTaskWithRequest(request) { data, response, error in
   if error != nil {
      println("error=\(error)")
      return
   }
            
   println("response = \(response)")
            
   let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
   println("responseString = \(responseString)")
}
task.resume();

這邊整理一下從網路上找的NSURLRequestCachePolicy相關資訊,轉載自:iOS内存缓存

  • ReloadIgnoringCacheData:忽略Cache數據,直接從原始網址下載。
  • ReloadRevalidatingCacheData:驗證本地的數據和遠端數據是否相同,如果不同則下载遠端数據,否则使用本地數據。
  • ReturnCacheDataDontLoad:只使用cache数據,如果不存在cache,請求失敗;用於没有建立網路連接離線模式;
  • ReturnCacheDataElseLoad: 只有在cache中不存在data時才從原始地址下载。
  • UseProtocolCachePolicy: NSURLRequest默認的cache policy,使用Protocol協議定義。
  • ReloadIgnoringLocalAndRemoteCacheData:忽略本地和遠端的緩存數據,直接從原始地址下载,與ReloadIgnoringCacheData類似。

[swift] 用Plist製作設定檔


很多APP都會有一些各人化設定檔,方便下次打開APP可以依照個人的喜好設定做出最適合的調整,接下來就實作如何將個人的設定檔存起來並在下次打開APP時Show出來
  • 首先先拉一個畫面,輸入框,確定按鈕,呈現文字的按鈕,我們的目標是在輸入框輸入的文字會被系統儲存起來,在下次App重開時依然顯示最後設定的文字
  • 基本的code如下,只要在輸入框填好後按下確定按鈕就可以更改文字了,但你只要重開APP他就又會回到最原本Button的文字
    import UIKit
    class plistViewController: UIViewController {
        @IBOutlet weak var txtbox: UITextField! //輸入文字框
        @IBOutlet weak var btnShowText: UIButton! //show文字的button
        @IBOutlet weak var btnSetting: UIButton! //確定按鈕
        
        override func viewDidLoad() {
            super.viewDidLoad();
        }
        
        @IBAction func btnSetting_click(sender: AnyObject) {
            //將輸入的文字設定給btnShowText
            btnShowText.setTitle(txtbox.text, forState: UIControlState.Normal);
        }
    }
    
  • 所以我們至少應該先把按鈕的文字改成抓Plist檔的來源,然後按下確定按鈕時將文字先存到plist檔中,但這邊有個觀念需要先釐清,基本上IOS APP是SandBox模式,所以只有特定資料夾內的檔案是可以寫入的,所以在這之前我們必須先新建一個Setting.plist檔案專門放設定值(建議不要用原本預設的info.plist,因為之後會移動這個檔案位置),然後在APP開啟時檢查這個檔案是否在可讀寫的資料夾之中了,如果沒有則複製一份過去,步驟如下

    先在Setting.plist新增儲存按鈕文字的欄位


    接著在AppDelegate.swift的didFinishLaunchingWithOptions寫下
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            // Override point for customization after application launch.
            
            //Setting Plist因為要可以讀寫,所以要移到Documents的folder底下
            let path:String = NSBundle.mainBundle().pathForResource("Setting", ofType: "plist")!;
            let destinationPath = String(format: "%@/Documents/Setting.plist", arguments:[NSHomeDirectory()]);
            var fm: NSFileManager = NSFileManager();
            if (!fm.fileExistsAtPath(destinationPath))
            {
                //如果在documents資料夾底下找不到該檔案,則複製一份過去
                fm.copyItemAtPath(path, toPath: destinationPath, error: nil);
            }
            return true
        }
    
    

    接下來將輸入的文字改存到plist中,然後按鈕文字的來源改成Setting.plist,全部程式碼如下
    import UIKit
    class plistViewController: UIViewController {
        @IBOutlet weak var txtbox: UITextField! //輸入文字框
        @IBOutlet weak var btnShowText: UIButton! //show文字的button
        @IBOutlet weak var btnSetting: UIButton! //確定按鈕
        
        override func viewDidLoad() {
            super.viewDidLoad();
            
            loadBtnText();
        }
        
        private func loadBtnText(){
            //button預設的字由Setting plist來
            btnShowText.setTitle(getSettingValue(), forState: UIControlState.Normal);
        }
        
        @IBAction func btnSetting_click(sender: AnyObject) {
            //將輸入的文字設定給btnShowText
            //btnShowText.setTitle(txtbox.text, forState: UIControlState.Normal);
        
            //修改成將輸入的文字存到Setting.plist後,在由那邊load給button
            Set_SettingPlist_Value(txtbox.text);
            loadBtnText();
        }
        
        //取得Setting.plist裡btnText欄位得值
        private func getSettingValue() -> String{
            var value:String!;
            //取得document folder底下的Setting.plist路徑
            let SettingPath = String(format: "%@/Documents/Setting.plist", arguments:[NSHomeDirectory()]);
            var dic:NSMutableDictionary? = NSMutableDictionary(contentsOfFile: SettingPath);
            
            //欄位名稱
            if (dic?.objectForKey("btnText") != nil){
                value = dic!.objectForKey("btnText") as String;
            }
            return value;
        }
        
        //把值存到Setting.plist裡面
        private func Set_SettingPlist_Value(value:String){
            let SettingPath = String(format: "%@/Documents/Setting.plist", arguments:[NSHomeDirectory()]);
            var dic:NSMutableDictionary = NSMutableDictionary(contentsOfFile: SettingPath)!;
            
            if (dic.objectForKey("btnText") != nil) {
                dic.setValue(value, forKey: "btnText");
                
                //true表示IOS會先將資料寫入一個輔助檔案中,然後再將這個檔案改為最後真正的目的地,避免出現錯誤
                dic.writeToFile(SettingPath, atomically: true);
            }
        }
    }
    
  • 接下來再試試看程式,你會發現即便把APP關掉,下次進來時button的文字會被記錄下來!!
    但這邊必須特別注意一點,隨著App的開發可能會對plist有刪減,重新部署到模擬器上時要記得先把舊的APP刪掉,讓Xode幫你重新安裝,原因是我們在AppDelegate.swift的didFinishLaunchingWithOptions寫了
    if (!fm.fileExistsAtPath(destinationPath))
    {
       //如果在documents資料夾底下找不到該檔案,則複製一份過去
       fm.copyItemAtPath(path, toPath: destinationPath, error: nil);
    }
    
    也就是說複製過一次後,之後的欄位有增減都無法改到Documents資料夾底下的那份plist了,所以只能刪除重裝!!

[swift] IOS地圖運用 (5) 計算點與點之間的距離


網路上有找到Google的計算公式,把它翻譯成Swift的語法後如下,回傳單位為公里
public static func GetDistance_Google(pointA:CLLocationCoordinate2D , pointB:CLLocationCoordinate2D) -> Double
{
   let EARTH_RADIUS:Double = 6378.137;
        
   let radlng1:Double = pointA.longitude * M_PI / 180.0;
   let radlng2:Double = pointB.longitude * M_PI / 180.0;
        
   let a:Double = radlng1 - radlng2;
   let b:Double = (pointA.latitude - pointB.latitude) * M_PI / 180;
   var s:Double = 2 * asin(sqrt(pow(sin(a/2), 2) + cos(radlng1) * cos(radlng2) * pow(sin(b/2), 2)));
        
   s = s * EARTH_RADIUS;
   s = (round(s * 10000) / 10000);
   return s;
}

[swift] IOS地圖運用 (4) 如何區分是User Location與Annotation


  • 接著上一篇,如果這時候我們再把mapkit view的user location打開來試試看,結果會發現user location的點被吃掉了,原因是我們改寫了viewForAnnotation,所有的點都會變成我們客製座標的樣式
  • 所以我們在viewForAnnotation補上判斷,讓user location不會被改變樣式
    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
            if(annotation is MKUserLocation){
                //return nil表示用預設值那個藍色發光圓形圖
                return nil;
            }
            
            var ann : MyAnnotation? = mapView.dequeueReusableAnnotationViewWithIdentifier("CustomerPoint") as? MyAnnotation;
            
            if(ann == nil){
                ann = MyAnnotation(annotation: annotation, reuseIdentifier: "CustomerPoint");
                ann?.canShowCallout = true;
            }
            ann?.DrawCustomerView();
            ann?.annotation = annotation;
            return ann;
    }
    
    結果如下,user location又出來摟~

2015年3月2日 星期一

[swift] IOS地圖運用 (3) 客製Annotation View


繼續接續上一篇,我們來把大頭針改成自己想要的樣子吧,已這張圖為例

  • 從上面那張圖可以看出來需要畫一個長方形,長方形裡面有一個title與圓形Icon,還需要一個倒三角形把它組回去,所以先來把這些元素準備一下吧
    首先先建立一個Class繼承MKAnnotationView
    import Foundation
    import MapKit
    public class MyAnnotation : MKAnnotationView  {
        var rectangoView:UIView!; //長方形
        var lblTitle:UILabel!;//Title
        var lblIcon:UILabel!;//圓形icon
        var circleView:UIView!; //原型view
        var triangle:UIView!; //三角形
        public override init(annotation: MKAnnotation!, reuseIdentifier: String!) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier);
        }
    
        public required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder);
        }
    }
    
  • 接著把每個圖形間的關係組合起來,例如圓形跟title都在大大的長方形裡面,所以先將他們add到rectangoView裡面
    import Foundation
    import MapKit
    public class MyAnnotation : MKAnnotationView  {
        var rectangoView:UIView!; //長方形
        var lblTitle:UILabel!;//Title
        var lblIcon:UILabel!;//圓形icon
        var circleView:UIView!; //原型view
        var triangle:UIView!; //三角形
        var DrawView:UIView!;//整個客製座標的畫布
        public override init!(annotation: MKAnnotation!, reuseIdentifier: String!) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier);
            
            //先把每個圖層間的關係建立好,例如圓形跟title都在大大的長方形裡面,所以先add進去
            lblTitle = UILabel();
            lblIcon = UILabel();
            rectangoView = UIView();
            circleView = UIView();
    
            circleView.addSubview(lblIcon);
            rectangoView.addSubview(lblTitle)
            rectangoView.addSubview(circleView);
    
            //畫出三角形遮罩
            //原理:將遮罩蓋到長方形上面,他會將不再這個框框範圍內的都過濾掉,就可以跑出三角形了
            let path:UIBezierPath = UIBezierPath ();
            path.moveToPoint(CGPoint(x: 0, y: 0));
            path.addLineToPoint(CGPoint(x: 10, y: 10));
            path.addLineToPoint(CGPoint(x: 20, y: 0));
            path.addLineToPoint(CGPoint(x: 0, y: 0));
    
            let masklayer:CAShapeLayer = CAShapeLayer ();//遮罩
            masklayer.path = path.CGPath;
            //遮罩遮出三角型
            triangle = UIView(frame: CGRect(x: 22, y: 28, width: 20, height: 20));
            triangle.layer.mask = masklayer;
            
            DrawView = UIView();
            DrawView.addSubview(rectangoView);
            DrawView.addSubview(triangle);
            
            self.addSubview(DrawView);
        }
    
        public required init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder);
        }
    }
    
  • 接著在MyAnnotation寫一個function來詳細畫這個View的內容
        public func DrawCustomerView() {
            //Title
            lblTitle.text = "5200萬";
            lblTitle.textColor = UIColor.whiteColor();
            lblTitle.font = UIFont.systemFontOfSize(12);
            lblTitle.sizeToFit();
            
            //大大的長方形
            rectangoView.frame = CGRect(x: 0, y: 0, width: lblTitle.frame.width + 30, height: 28);//整體
            rectangoView.backgroundColor =  UIColor(red: 0.208, green:0.596 , blue: 0.859, alpha: 1);
            rectangoView.layer.cornerRadius = 3;//設定圓角
            
            //“icon”圓型小字
            lblIcon.font = UIFont.systemFontOfSize(11);
            lblIcon.font = UIFont.boldSystemFontOfSize(14);
            lblIcon.text = "成";
            lblIcon.textColor = rectangoView.backgroundColor;
            lblIcon.sizeToFit();
            lblIcon.center = CGPoint (x: 10, y: 10);
            
            //圓形
            circleView.frame = CGRect(x: 2, y: 4, width: 20, height: 20);//圓形標籤
            circleView.backgroundColor = UIColor.whiteColor();
            circleView.layer.cornerRadius = 10;
            circleView.alpha = 1;
            
            //畫三角形
            triangle.backgroundColor =  UIColor(red: 0.208, green:0.596 , blue: 0.859, alpha: 1);
            //rectangoView.Transform 旋轉
              
            DrawView.frame = CGRect(x: 0, y: 0, width:triangle.frame.width,height: 48);
        }
    
  • 接下來修改view controller的viewForAnnotation事件如下
        func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
            var ann : MyAnnotation? = mapView.dequeueReusableAnnotationViewWithIdentifier("CustomerPoint") as? MyAnnotation;
            
            if(ann == nil){
                ann = MyAnnotation(annotation: annotation, reuseIdentifier: "CustomerPoint");
                ann?.canShowCallout = true;
            }
            ann?.DrawCustomerView();
            ann?.annotation = annotation;
            return ann;
        }
    
  • 立馬執行看看,你會發現發生錯誤在MyAnnotation class,而且錯誤訊息寫得不清不楚的,這是第一個地雷,雖然繼承MKAnnotationView沒有要求一定要實作init(frame: CGRect),但不做會壞掉喔,那到底幹麻不列為required阿!!!所以乖乖補上到MyAnnotation就正常摟
        override init(frame: CGRect) {
            super.init(frame: frame);
        }
    
  • 這時候會發現畫面長這樣...

    請在DrawCustomerView這個地方補上下面這段code,讓文字位移後執行看看會變得正常多了
    lblTitle.frame.origin.x = 24;//中心位移
    lblTitle.frame.origin.y = 7;
    
  • 接下來我們測試一下這個畫出來的點是否跟插大頭針指的位置相同,結果.....歪掉了
  • 但我們可以看出來他是在針頭的地方開始畫,所以來做點加工吧,在DrawCustomerView()最後面加上下面的code讓整個View位移來符合需求
            self.frame = CGRect(x: 0, y: 0, width: rectangoView.frame.width, height: rectangoView.frame.height);
            self.centerOffset = CGPoint(x: 0, y: -21);
    
    我們再看看...
  • 看起來一切正常了,把一些測試的程式碼拿掉後完整如下
    view controller
    import UIKit
    import CoreLocation
    import MapKit
    class MapViewController: UIViewController,CLLocationManagerDelegate , MKMapViewDelegate{
    
        @IBOutlet weak var uimap: MKMapView!//地圖元件
        var location : CLLocationManager!; //座標管理元件
        override func viewDidLoad() {
            super.viewDidLoad();
    
            location = CLLocationManager();
            location.delegate = self;
            
            //詢問使用者是否同意給APP定位功能
            location.requestWhenInUseAuthorization();
            //開始接收目前位置資訊
            location.startUpdatingLocation();
            location.distanceFilter = CLLocationDistance(10); //表示移動10公尺再更新座標資訊
            
            uimap.delegate = self;
        }
    
        override func viewDidDisappear(animated: Bool) {
            //因為GPS功能很耗電,所以被敬執行時關閉定位功能
            location.stopUpdatingLocation();
        }
        
        func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
            //取得目前的座標位置
            let c = locations[0] as CLLocation;
            let nowLocation = CLLocationCoordinate2D(latitude: c.coordinate.latitude, longitude: c.coordinate.longitude);
            
            //將map中心點定在目前所在的位置
            //span是地圖zoom in, zoom out的級距
            let _span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.0005, longitudeDelta: 0.0005);
            self.uimap.setRegion(MKCoordinateRegion(center: nowLocation, span: _span), animated: true);
            
            //加入座標
            addPointAnnotation(c.coordinate.latitude, longitude: c.coordinate.longitude);
        }
        
        //新增座標
        private func addPointAnnotation(latitude:CLLocationDegrees , longitude:CLLocationDegrees){
            //大頭針
            var point:MKPointAnnotation = MKPointAnnotation();
            //設定大頭針的座標位置
            point.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude);
            uimap.addAnnotation(point);
        }
        
        func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
            var ann : MyAnnotation? = mapView.dequeueReusableAnnotationViewWithIdentifier("CustomerPoint") as? MyAnnotation;
            
            if(ann == nil){
                ann = MyAnnotation(annotation: annotation, reuseIdentifier: "CustomerPoint");
                ann?.canShowCallout = true;
            }
            ann?.DrawCustomerView();
            ann?.annotation = annotation;
            return ann;
        }
    }
    
    MyAnnotation Class完整如下
    import Foundation
    import MapKit
    public class MyAnnotation : MKAnnotationView  {
        var rectangoView:UIView!; //長方形
        var lblTitle:UILabel!;//Title
        var lblIcon:UILabel!;//圓形icon
        var circleView:UIView!; //原型view
        var triangle:UIView!; //三角形
        var DrawView:UIView!;//整個客製座標的畫布
        
        override init(annotation: MKAnnotation!, reuseIdentifier: String!) {
            super.init(annotation: annotation, reuseIdentifier: reuseIdentifier);
            
            //先把每個圖層間的關係建立好,例如圓形跟title都在大大的長方形裡面,所以先add進去
            lblTitle = UILabel();
            lblIcon = UILabel();
            rectangoView = UIView();
            circleView = UIView();
    
            circleView.addSubview(lblIcon);
            rectangoView.addSubview(lblTitle)
            rectangoView.addSubview(circleView);
    
            //畫出三角形遮罩
            //原理:將遮罩蓋到長方形上面,他會將不再這個框框範圍內的都過濾掉,就可以跑出三角形了
            let path:UIBezierPath = UIBezierPath ();
            path.moveToPoint(CGPoint(x: 0, y: 0));
            path.addLineToPoint(CGPoint(x: 10, y: 10));
            path.addLineToPoint(CGPoint(x: 20, y: 0));
            path.addLineToPoint(CGPoint(x: 0, y: 0));
    
            let masklayer:CAShapeLayer = CAShapeLayer ();//遮罩
            masklayer.path = path.CGPath;
            //遮罩遮出三角型
            triangle = UIView(frame: CGRect(x: 22, y: 28, width: 20, height: 20));
            triangle.layer.mask = masklayer;
            
            DrawView = UIView();
            DrawView.addSubview(rectangoView);
            DrawView.addSubview(triangle);
            
            self.addSubview(DrawView);
        }
    
        public required  init(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder);
        }
        
        override init(frame: CGRect) {
            super.init(frame: frame);
        }
        
        public func DrawCustomerView() {
            //Title
            lblTitle.text = "5200萬";
            lblTitle.textColor = UIColor.whiteColor();
            lblTitle.font = UIFont.systemFontOfSize(12);
            lblTitle.frame.origin.x = 24;//中心位移
            lblTitle.frame.origin.y = 7;
            lblTitle.sizeToFit();
            
            //大大的長方形
            rectangoView.frame = CGRect(x: 0, y: 0, width: lblTitle.frame.width + 30, height: 28);//整體
            rectangoView.backgroundColor =  UIColor(red: 0.208, green:0.596 , blue: 0.859, alpha: 1);
            rectangoView.layer.cornerRadius = 3;//設定圓角
            
            //“icon”圓型小字
            lblIcon.font = UIFont.systemFontOfSize(11);
            lblIcon.font = UIFont.boldSystemFontOfSize(14);
            lblIcon.text = "成";
            lblIcon.textColor = rectangoView.backgroundColor;
            lblIcon.sizeToFit();
            lblIcon.center = CGPoint (x: 10, y: 10);
            
            //圓形
            circleView.frame = CGRect(x: 2, y: 4, width: 20, height: 20);//圓形標籤
            circleView.backgroundColor = UIColor.whiteColor();
            circleView.layer.cornerRadius = 10;
            circleView.alpha = 1;
            
            //畫三角形
            triangle.backgroundColor =  UIColor(red: 0.208, green:0.596 , blue: 0.859, alpha: 1);
            //rectangoView.Transform 旋轉
            
            
            DrawView.frame = CGRect(x: 0, y: 0, width:triangle.frame.width,height: 48);
            self.frame = CGRect(x: 0, y: 0, width: rectangoView.frame.width, height: rectangoView.frame.height);
            self.centerOffset = CGPoint(x: 0, y: -21);
        }
    }
    

[swift] IOS地圖運用 (2) 在地圖釘上大頭針


承上一篇的Code我們繼續往下延伸,如何在地圖釘上大頭針呢?
首先我們把mapkit view的user location選項關閉,改成自己放上去的大頭針試試看

  • 把程式修改如下,這邊主要著重在addPointAnnotation這個function
    import UIKit
    import CoreLocation
    import MapKit
    class MapViewController: UIViewController,CLLocationManagerDelegate {
    
        @IBOutlet weak var uimap: MKMapView!//地圖元件
        var location : CLLocationManager!; //座標管理元件
        override func viewDidLoad() {
            super.viewDidLoad();
    
            location = CLLocationManager();
            location.delegate = self;
            
            //詢問使用者是否同意給APP定位功能
            location.requestWhenInUseAuthorization();
            //開始接收目前位置資訊
            location.startUpdatingLocation();
            location.distanceFilter = CLLocationDistance(10); //表示移動10公尺再更新座標資訊
        }
    
        override func viewDidDisappear(animated: Bool) {
            //因為GPS功能很耗電,所以被敬執行時關閉定位功能
            location.stopUpdatingLocation();
        }
        
        func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
            //取得目前的座標位置
            let c = locations[0] as CLLocation;
            let nowLocation = CLLocationCoordinate2D(latitude: c.coordinate.latitude, longitude: c.coordinate.longitude);
            
            //將map中心點定在目前所在的位置
            //span是地圖zoom in, zoom out的級距
            let _span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.0005, longitudeDelta: 0.0005);
            self.uimap.setRegion(MKCoordinateRegion(center: nowLocation, span: _span), animated: true);
            
            //加入座標
            addPointAnnotation(c.coordinate.latitude, longitude: c.coordinate.longitude);
        }
        
        //新增座標
        private func addPointAnnotation(latitude:CLLocationDegrees , longitude:CLLocationDegrees){
            //大頭針
            var point:MKPointAnnotation = MKPointAnnotation();
            //設定大頭針的座標位置
            point.coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude);
            point.title = "I'm here";
            point.subtitle = "緯度:\(latitude) 經度:\(longitude)";
            
            uimap.addAnnotation(point);
        }
    }
    
    執行看看成果,點擊大頭貼可以看到剛剛設定的title與subtitle
  • 那如果想將大頭針變色呢?
    首先先將view controller繼承MKMapViewDelegate,並將mapview kit delegate指定為自己這個veiew controller
    uimap.delegate = self;
    
  • 接著override mapview的viewForAnnotation事件
    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
       var pointView : MKPinAnnotationView? = mapView.dequeueReusableAnnotationViewWithIdentifier("CustomerPoint") as?MKPinAnnotationView;
            
       if(pointView == nil){
          pointView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "CustomerPoint");
          //因為已經改寫了Annotation View,所以要將canShowCallout改成true,否則點擊大頭針不會跑出來我們剛剛指定的title跟subtitle
          pointView?.canShowCallout = true;
       }
       pointView!.pinColor = MKPinAnnotationColor.Green;
        
       return pointView;
    }
    
  • 結果如下
  • 這邊應該看出來整個Annotation的顯示方式都可以透過viewForAnnotation這個事件來改寫,所以下一篇就來介紹如何客製化Annotation的View

[swift] IOS地圖運用 (1) 取得目前座標位置


地圖資訊是手機很大量運用的資訊之一,接下來是來整理關於目前學到的MapKit View地圖的運用

  • 首先先拉一個新的ViewController,並將MapKit View拉近去
  • 接著在ViewController建立參數與MapKit View做對應,這邊需要注意的地方是用Map元件需要import MapKit
    import UIKit
    import MapKit
    class MapViewController: UIViewController {
    
        @IBOutlet weak var uimap: MKMapView!//地圖元件
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    }
    
  • 取得座標資訊是由一個叫要做CLLocationManager來控制的,又IOS8取得座標資訊必須先取得使用者的授權才可以,所以必須到Info.plist新增NSLocationAlwaysUsageDescription或NSLocationWhenInUseUsageDescription兩種鍵值,而前者表示不管APP在前景或背景執行都會持續取得座標資訊,而後者只有在需要時才會去取座標定位資訊,而這兩個鍵值分別對應到CLLocationManager的requestAlwaysAuthorization和requestWhenInUseAuthorization,這邊的範例已NSLocationWhenInUseUsageDescription為例
    import UIKit
    import CoreLocation
    import MapKit
    class MapViewController: UIViewController,CLLocationManagerDelegate {
    
        @IBOutlet weak var uimap: MKMapView!//地圖元件
        var location : CLLocationManager!; //座標管理元件
        override func viewDidLoad() {
            super.viewDidLoad();
    
            location = CLLocationManager();
            location.delegate = self;
            
            //詢問使用者是否同意給APP定位功能
            location.requestWhenInUseAuthorization();
            //開始接收目前位置資訊
            location.startUpdatingLocation();
        }
    
        override func viewDidDisappear(animated: Bool) {
            //因為GPS功能很耗電,所以被敬執行時關閉定位功能
            location.stopUpdatingLocation();
        }
        
        func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {
            //取得目前的座標位置
            let c = locations[0] as CLLocation;
            //c.coordinate.latitude 目前緯度
            //c.coordinate.longitude 目前經度
            let nowLocation = CLLocationCoordinate2D(latitude: c.coordinate.latitude, longitude: c.coordinate.longitude);
            
            //將map中心點定在目前所在的位置
            //span是地圖zoom in, zoom out的級距
            let _span:MKCoordinateSpan = MKCoordinateSpan(latitudeDelta: 0.0005, longitudeDelta: 0.0005);
            self.uimap.setRegion(MKCoordinateRegion(center: nowLocation, span: _span), animated: true);
        }
    }
    
    接著別忘了去Info.plist新增NSLocationWhenInUsageDescription

  • 接著執行看看!!

    詢問是否授權取得座標資訊

    地圖中心點移到你目前的位置摟~
  • 順帶一提,如果需要把使用者的位置座標顯示出來,在mapkit view設定檔將User Location打勾即可!!


  • 如果你有實際run過這段程式應該會發現你滑動map時,它會一直彈回你目前所在的位置,原因是我們沒有指定移動多少距離才會更新座標位置,所以didUpdateLocations會一直被呼叫,也就一直被彈回你的所在位置摟,告訴app移動多少距離再更新座標資訊即可
    location.distanceFilter = CLLocationDistance(10); //表示移動10公尺再更新座標資訊
    

[swift]Navigation Controller (2) 傳遞參數


如果要在換頁時讓PageA傳遞數值給PageB,並且在PageB顯示傳遞的參數時作法如下

  • 先在PageB的Controller新增一個Label,我們將用它來顯示PageA究竟傳了什麼過來
    import UIKit
    class Page_B_ViewController: UIViewController {
        @IBOutlet weak var lblValue: UILabel!  //顯示傳遞數值的Label
        var fromA_Value:String!;//儲存Page_A傳遞過來的數值
        override func viewDidLoad() {
            super.viewDidLoad();
            
            lblValue.text = fromA_Value;
        }
    }
    
  • 接著在PageA Controller新增prepareForSegue事件
    import UIKit
    class Page_A_ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            //NavBar
            self.navigationController?.navigationBar.barTintColor = UIColor(red: 0.58, green: 0.761, blue: 0.231, alpha: 1)
            let titleDic : NSDictionary = [NSForegroundColorAttributeName : UIColor.whiteColor()]; //字的顏色
            self.navigationController?.navigationBar.titleTextAttributes = titleDic;
            self.navigationController?.navigationBar.tintColor = UIColor.whiteColor() //按鈕的顏色
            let barOptionIcon : UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon-options"), style: UIBarButtonItemStyle.Plain, target: self, action: "PushToOptionController:");
            self.navigationController?.navigationBar.topItem?.setRightBarButtonItem(barOptionIcon, animated: true) ;
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
        
        //右上角Button按下時觸發的事件
        func PushToOptionController(sender: UIBarButtonItem) {
            self.performSegueWithIdentifier("toPageB", sender: self);
        }
        
        //準備跳轉頁面的設定
        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
            if(segue.identifier == "toPageB"){
                var page_b:Page_B_ViewController? = segue.destinationViewController as? Page_B_ViewController
                if(page_b != nil){
                    page_b?.fromA_Value = "來自A的數值";
                }
            }
        }
    }
    
  • 完成!!!

[swift]Navigation Controller (1) 畫面切換



  • 先拉一個Navigation Controller到StoryBoard,並將起始指標指向它
  • 接著拉個ViewA並且設定他為root view並且執行看看


    執行結果首頁就是剛剛指定root view的那一頁了
  • 接著在拉第二個ViewB,並將ViewA與ViewB做Push的關聯


    接著設定這個push segue的identifier
  • 在ViewA做已下修改
    import UIKit
    class Page_A_ViewController: UIViewController {
    
     override func viewDidLoad() {
       super.viewDidLoad()
            
       //NavBar
       self.navigationController?.navigationBar.barTintColor = UIColor(red: 0.58, green: 0.761, blue: 0.231, alpha: 1)
       let titleDic : NSDictionary = [NSForegroundColorAttributeName : UIColor.whiteColor()]; //字的顏色
       self.navigationController?.navigationBar.titleTextAttributes = titleDic;
       self.navigationController?.navigationBar.tintColor = UIColor.whiteColor() //按鈕的顏色
       //設定右上角的按鈕   
    let barOptionIcon : UIBarButtonItem = UIBarButtonItem(image: UIImage(named: "icon-options"), style: UIBarButtonItemStyle.Plain, target: self, action: "PushToOptionController:");
       self.navigationController?.navigationBar.topItem?.setRightBarButtonItem(barOptionIcon, animated: true) ;
     }
    
     //右上角Button按下時觸發的事件
     func PushToOptionController(sender: UIBarButtonItem) {
        self.performSegueWithIdentifier("toPageB", sender: self);
     }
    
    }
    
  • 完成後試試看,點擊右上角的按鈕即可跳到第二頁了!!