用Swift實現一款天氣預報APP(三)

這個系列的目錄:html

用Swift實現一款天氣預報APP(一)git

用Swift實現一款天氣預報APP(二)github

用Swift實現一款天氣預報APP(三)api

 

經過前面的學習,一個天氣預報的APP已經基本可用了。至少能夠查看如今當前的天氣狀況和將來幾個小時的天氣預報了。可是,還不夠完善。若是用戶想要知道他要去的地方的天氣怎麼辦。明顯咱們的APP在目前來講沒法知足用戶的這個需求。而咱們的APP須要獲取其餘城市的天氣卻很是的簡單。經過查看天氣的API,發現只要把城市的名稱做爲參數就能夠得到當地城市的天氣預報。API:服務器

api.openweathermap.org/data/2.5/find?q=London&type=like&mode=xml

q=London就是在API中指明地點的參數。可是,從這裏也能夠單出。這個城市的名稱顯示是須要英文的,不是「北京」這樣的漢字,也就是說在城市列表中顯示的是漢字,可是傳給API的url使用的時這個城市的英文名稱,或者能夠說是拼音字母。ide

在這一篇中,咱們主要要實現的功能是切換城市,和刷新天氣預報數據。首先在Storyboard中添加一個叫作城市列表的Controller。咱們須要在Controller中顯示一個城市列表,這裏須要用到一個在iOS的開發中很經常使用的控件:UITableView。添加一個UIViewController以後,拖動一個UITableView到這個ViewController上。這樣,在界面上來講就齊全了。學習

下面,建立代碼,選擇Source,先在Subclass of選擇你的超類爲UIViewController,而後命名你的類的名稱。這裏是"CityListViewController"。最後在語言一項選擇Swift。你應該不會選擇Objective-C的。如圖:url

以後一路的Next一直到Done。spa

不少的教程在講到UITableView的時候老是喜歡用UITableViewController,這個是對用包含一個UITableView的UIViewController的封裝。有的時候,系統封裝了過多的東西對於開發者來講並非什麼好事。尤爲是開發者單獨處理UITableView也不會耗費太多的時間。因此,這裏使用的是UIViewController和UITableView的組合。3d

在你的ViewController文件中添加準備綁定的UITableView對象的屬性:

@IBOutlet weak var tableView: UITableView!

以後在Controller裏選擇以後,在右邊欄裏選擇左數第三個選項,而後在下面的Class裏選擇你剛剛建立的CityListViewController。通常,在你選擇完了

Controller以後,Class下面的Module會自動設定爲Current-Swift_Weather。也就是會自動選擇你的項目名稱。若是沒有選擇的話,你須要手動添加你的項目名稱到Module裏。不然,這個ViewController是不可用的。Swift中引入了Module(模塊)這個概念。默認的你的APP就是一個Module。類都是從你的應用的Module裏查找的。若是沒有這個Module名稱的話,應用沒法找到你給這個ViewController關聯的代碼。

 

 

這些操做完成以後,你已經把Storyboard的ViewController和對應的代碼關聯在了一塊兒。下面還須要關聯上前文提到的UITableView控件。點擊你剛剛選中的controller而後在右邊欄中選擇最右邊的箭頭按鈕你會看到裏面會出現一個tableView,他後面的小圓圈尚未和Storyboard的TableView關聯起來。鼠標放在小圓圈上,按下Ctrl同時移動鼠標到Storyboard的UITableView上。

到目前爲止都很完美,可是這個TableView還不能用。TableView須要知道有多少個TableViewCell要顯示出來,每個Cell上面顯示什麼內容。每個Cell有多高,有多少個Section。哪一個Cell被選中,哪一個Cell從被選中變成了麼有被選中等等。。。這些都是經過TableView的代理實現的。這樣的代理一共有兩個,一個叫作UITableViewDelegate,一個叫作UITableViewDataSource

因此,還須要把UITableView的兩個代理和UITableView所在的Controller關聯。選中Storyboard的UITableView,而後選擇右邊欄的最後一個選項。就是最後的那個選項。你會看到

把鼠標放在小圓圈上,同時按下Ctrl鍵,移動鼠標到這個UITableView所在的Controller上,準確的說是移動到這裏:。兩個都是這樣的操做。完成以後就給這個UITableView關聯好了UITableViewDelegateUITableViewDataSource

到目前爲止,咱們在Storyboard中建立了一個Controller(Scene),在上面放了一個UITableView。建立了一個UIViewController代碼,而且和剛剛建立的Controller關聯到了一塊兒。而且把UITableView也關聯到了一塊兒,同時關聯的還有這個UITableView的UITableViewDelegateUITableViewDataSourceUITableViewDelegateUITableViewDataSource在Swift的語法上來講都是protocol,也就是其餘的語言,如Java、C#的接口,哪裏繼承了哪裏就要給出實現。既然UITableView的這delegate和datasource都制定在了他所在的Controller上,那麼咱們代碼裏的CityListViewController就須要繼承UITableViewDelegateUITableViewDataSource並實現這兩個protocol。

class CityListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource

下面是UITableViewDelegateUITableViewDataSource中部分必要的方法的實現。注意,這些只是一個UITableView正常顯示的必要方法,還有不少的方法暫時沒有用到。

第一行,是指定這個UITableView中有多少個section的,section分區,一個section裏會包含多個Cell。這裏,是隻有一個section。

第二行,是指定每個section裏面有多少個Cell的。由於咱們只有一個section,因此,有多少個城市可選就有多少個Cell。這個是視不一樣狀況定的。

第三行,初始化每個Cell。一個Cell長什麼樣子就由這個方法決定。

第四行,是選中一個Cell後執行的方法。當用戶選擇了一個Cell的時候,咱們須要知道是哪個,並把這個Cell的城市的英文名稱(或者是拼音的字母)發送到主界面中用於獲取該城市的天氣數據。

這些,就是使用一個UITableView時的所有了。首先建立一個放UITableView的Controller(Scene)而後拖動一個UITableView在上面。二,建立一個對應於這個Scene的Controller的Swift代碼,並在代碼中添加UITableView屬性。關聯Scene和Swift的Controller,關聯代碼的UITableView屬性和Storyboard中的UITableView。三,關聯UITableView的delegate和datasource到Swift代碼的Controller,並在其中繼承和實現UITableViewDelegateUITableViewDataSource。這一部分須要多練習而且熟記。由於你會發現沒有一個應用不用到UITableView的。若是你找不出UITableView那也多是開發者對於UITableView的定製比較深,直觀上看不出來而已。

這裏必須強調的一點,就是第三行的建立Cell的方法。UITableView的Cell不是每次用到都去建立的。手機現在的內存已經有2G的了,CPU也是幾核心的。可是,其資源仍是相對比較緊缺。若是,每一個Cell都新建一個。那麼,用戶在上下滑動UITableView的時候會很是的卡頓。這對於一個好的APP時絕對不容許的。因此蘋果也推薦了一種使用Cell的方法,若是Cell尚未被建立的話就建立一個。若是Cell已經建立了,那麼就對這個Cell從新賦值。這裏一點很關鍵,若是一個Cell已經建立了,只從新賦值而再也不建立!參見下面的代碼:

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier") as? UITableViewCell
        if cell == nil {
            cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CellIdentifier")
        }
        
        cell?.textLabel.text = self.cityList.values.array[indexPath.row]
        
        return cell!
    }

首先按照Cell的Identifier從UITableView的Cell重用隊列獲取Cell。若是爲空則建立一個Cell,並指定這個Cell的Identifier。若是Cell不爲空的話給這個Cell的textLabel的text屬性從新賦值。可是,既然咱們用了Storyboard了,就用的完全一點。Cell爲何不用Storyboard來建立呢。這樣又會省去不少的代碼,好比咱們這裏的重用Cell的部分。在右邊欄中找到UITableViewCell,並拖動到UITableView上。給這個Cell的Identifier起個名字就叫"cityCell"。Style選擇Custom,表示咱們要自定義這個Cell。如圖:

接下來,添加爲這個Cell添加新的Swift代碼文件。首先,source->Cocoa Touch Class。以後選擇

給Cell綁定Swift代碼類。選中Storyboard的這裏

 

 

 

 

 

以後->

 

 

 

這個Cell須要一個UILabel來顯示城市的中文名稱(這個UILabel只是爲了代表在Storyboard中自定義一個Cell的時候該如何處理,通常來講Cell中有一個textLabel能夠顯示文本)。那麼先在代碼中添加一個label的屬性:

@IBOutlet weak var cityNameLabel: UILabel!

添加完屬性以後,關聯這cityNameLabel和Storyboard中的Cell中剛剛添加的Label。重複上面說到的第一步,選中這個cell。而後選擇第二步中最上面選項中的最後一項。你就會看到cityNameLabel和他後面是小圓圈。你應該知道怎麼辦了。關聯屬性和Storyboard的Label以後,回到前面說道的建立Cell的方法。

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cityListCell = tableView.dequeueReusableCellWithIdentifier("cityCell", forIndexPath: indexPath) as CityListTableViewCell
        cityListCell.cityNameLabel.text = self.cityList.values.array[indexPath.row]
        return cityListCell
    }

看到有什麼不一樣的麼。是的,這裏不用再從TableView的Cell的複用隊列中獲取Cell了。由於,這些都在Storyboard中處理好了。咱們只要每次給Cell從新賦值就能夠了。

到目前爲止,這個城市列表仍是不能用的。由於,咱們尚未把這個列表和天氣預報的主界面關聯起來。是的,用戶從哪裏進入這個城市列表呢。如今咱們就把主界面和城市列表關聯起來。首先,在Storyboard上拖入一個UINavigationController。刪掉後面的RootViewController,並把這個UINavigationControllerRootViewController和咱們剛剛建立的城市列表Controller關聯起來。這一相似操做在第一部分中講過。不清楚的話能夠從新看看第一部分。

以後,找到主界面的City按鈕,鏈接到新添加的UINavigationController上,在彈出的Action Segue中選擇modal。這個時候運行APP,點擊City就會出現剛剛建立的CityListController了,用戶能夠點這上面的某一行,可是。。。回不去了。如今就來處理這個問題。開發這個工做就是「逢山開路,遇水搭橋」。在MainViewController中添加以下代碼

    @IBAction func dismissCityListController(segue: UIStoryboardSegue){

        println("dismiss controller")

    }

名字能夠任意起,可是參數必須是UIStoryboardSegue類型的。而後,在Storyboard的天氣預報主界面中右擊Exit,在出現的菜單中你會看到剛剛添加的方法dismissCityListController。在這個方法有個小圓圈。Storyboard裏就是充滿了這樣的小圓圈。把這個小圓圈鏈接到CityListController的Cell上,在彈出的小菜單中選擇selection。也就是說在用戶選擇了UITableView的一個Cell的時候(selection)CityListController就會「Exit」退出。

這個方法就是傳說中的「unwind segue」給這個segue設定一個identifier爲「backToMain」。鏈接好之後再運行APP。在用手指點了一個City之後CityListController就會隱藏起來。用戶能夠在選擇了城市之後返回繼續操做。

可是,選擇了城市之後主界面顯示的仍是原來的城市,並無更換。那時由於,咱們並無添加相關的代碼。上面添加的只是讓界面能夠在segue的引導之下跳轉,可是沒有更換城市後從新請求天氣預報數據。下面就完成這一部分功能。

UIViewController之間傳遞數據,咱們這裏是從CityListController傳遞選擇好的城市給MainViewController。方法有不少,好比,之後你會常常用到的Notification的方法。用戶選擇一個城市以後發出一個Notification,在MainviewController中捕獲這個Notification並作相應的處理。看似很簡單把。不過咱們這裏不用這個方法。用Notification的方法會給代碼的維護形成必定的困擾。哪裏發送,哪裏接受都是分開寫的,不容易維護代碼。咱們這裏要將的時用代理的方式傳遞數據。這個中方法在自定義控件,和ViewController之間都會常常用到。具體到咱們的天氣APP這裏,咱們須要從CityListController傳遞數據給MainViewController,那麼就在CityListController文件中定義一個接口(protocol)。蘋果通常的命名規則是你的Controller的名字後面加Delegate。這樣很是好辨認哪裏是定義Delegate的,哪裏是用到這個Delegate的。

@objc protocol CityListViewControllerDelegate{
    func cityDidSelected(cityKey: String)
}

這個@objc不是必定要加的,通常是和其餘的Objective-C代碼共用的時候加。在CityListViewController中添加一個叫作delegate的屬性。這個屬性會在CityListViewController的UITableView裏的某個Cell選中時被執行。

@IBOutlet weak var delegate: CityListViewControllerDelegate?

這裏用weak時由於,咱們不但願這個代理強引用(strong)其餘的Controller。這樣會形成循環引用,使得連個Controller的引用計數不減小,從而沒法在不用的時候從內存清除。在選中的時候執行

    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        println("did select row \(indexPath.row)")
        
        // set current selected index of city list
        self.selectedIndex = indexPath.row
        if self.selectedIndex != nil && self.delegate != nil {
            var cityKey = self.cityList.keys.array[self.selectedIndex!]
            self.delegate?.cityDidSelected(cityKey)
        }
        tableView.deselectRowAtIndexPath(indexPath, animated: true)
    }

在CityListViewController中執行代理的方法的話,就須要在MainViewController中實現這個protocol。

class MainViewController: UIViewController, CLLocationManagerDelegate, CityListViewControllerDelegate{

//MARK: city changed delegate func cityDidSelected(cityKey: String){ println("selected city \(cityKey)") }
}

上面的代碼是一個大概是實現。具體的代碼能夠在示例工程中查看。

運行APP,並選擇一個其餘的城市的時候,這段代碼就會執行。在Console中會出現選擇的城市的英文名稱。如今咱們須要來真的了。查看以前的代碼,有一個方法func updateWeatherInfo(latitude: CLLocationDegrees, longitude: CLLocationDegrees)會在獲取用戶的地理位置後請求服務器得到天氣預報。咱們如今是須要根據城市的名稱獲取天氣預報了。那麼,咱們就來Over Load方法updateWeatherInfoOver Load就是定義一個和某個別的方法同名可是參數不一樣的方法。

func updateWeatherInfo(cityName: String)

之後的事情就是在前文中關於HTTP請求的url字符串問題了。這裏很少作敘述。

還有一個功能沒有完成。你很快會想到:刷新(refresh)。用戶在選擇了其餘的城市的時候,須要很快回到用戶點擊刷新時所在位置的天氣預報。點擊Refresh的時候獲取用戶的最新地理位置數據,並請求天氣預報數據。這個功能已經實現。在用戶進入主界面的時候就會自動獲取用戶的位置,並請求天氣預報。刷新功能只須要在在refreshAction方法中從新獲取用戶的地理位置就能夠,請求天氣預報會在得到用戶位置後自動執行。這裏須要提到的一點是,當成功獲取了天氣預報以後,就應該中止獲取用戶地理位置。由於這樣會給用戶省電!省電也是用戶體驗的一部分。做爲一個開發者,若是你的APP太多費電,並且還不是一個很好玩的遊戲的話實在是說不過去的。

本系列文章的代碼在這裏

延伸閱讀:AFNetworking是cocoaPods加載進來的。瞭解更多cocoaPods,請看這裏

本文所使用的代碼的原始版本是來自Github的這裏。咱們如今的代碼已經比原做者的豐富的多了。不過仍是要感謝原做者。

相關文章
相關標籤/搜索