这个系列的目录:html
用Swift实现一款天气预报APP(二)github
经过前面的学习,一个天气预报的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关联好了UITableViewDelegate和UITableViewDataSource。
到目前为止,咱们在Storyboard中建立了一个Controller(Scene),在上面放了一个UITableView。建立了一个UIViewController代码,而且和刚刚建立的Controller关联到了一块儿。而且把UITableView也关联到了一块儿,同时关联的还有这个UITableView的UITableViewDelegate和UITableViewDataSource。UITableViewDelegate和UITableViewDataSource在Swift的语法上来讲都是protocol,也就是其余的语言,如Java、C#的接口,哪里继承了哪里就要给出实现。既然UITableView的这delegate和datasource都制定在了他所在的Controller上,那么咱们代码里的CityListViewController就须要继承UITableViewDelegate和UITableViewDataSource并实现这两个protocol。
class CityListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
下面是UITableViewDelegate和UITableViewDataSource中部分必要的方法的实现。注意,这些只是一个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,并在其中继承和实现UITableViewDelegate和UITableViewDataSource。这一部分须要多练习而且熟记。由于你会发现没有一个应用不用到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,并把这个UINavigationController的RootViewController和咱们刚刚建立的城市列表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方法updateWeatherInfo。Over Load就是定义一个和某个别的方法同名可是参数不一样的方法。
func updateWeatherInfo(cityName: String)
之后的事情就是在前文中关于HTTP请求的url字符串问题了。这里很少作叙述。
还有一个功能没有完成。你很快会想到:刷新(refresh)。用户在选择了其余的城市的时候,须要很快回到用户点击刷新时所在位置的天气预报。点击Refresh的时候获取用户的最新地理位置数据,并请求天气预报数据。这个功能已经实现。在用户进入主界面的时候就会自动获取用户的位置,并请求天气预报。刷新功能只须要在在refreshAction方法中从新获取用户的地理位置就能够,请求天气预报会在得到用户位置后自动执行。这里须要提到的一点是,当成功获取了天气预报以后,就应该中止获取用户地理位置。由于这样会给用户省电!省电也是用户体验的一部分。做为一个开发者,若是你的APP太多费电,并且还不是一个很好玩的游戏的话实在是说不过去的。
本系列文章的代码在这里。
延伸阅读:AFNetworking是cocoaPods加载进来的。了解更多cocoaPods,请看这里。
本文所使用的代码的原始版本是来自Github的这里。咱们如今的代码已经比原做者的丰富的多了。不过仍是要感谢原做者。