从学习iOS开发到现在,入门已经有一段时间了,第一次真正意义上自主实现一个app,还是蛮有成就感的,下面就简单记录与分享一下实现的过程。
目录
先上最终效果
基本思路
- 用Main.storyboard搭建原始页面
- 请求获取位置
- 根据坐标发起网络请求
- 解析天气数据
- 将控件内容与获取的数据联系
- 配置第二个页面
- 根据城市名发起网络请求
- MVC分离代码
一、搭建页面
1、创建项目文件
2、选择Main.storyboard文件,点击右上角加号添加所需控件,并通过通过下方功能按键设置控件的约束,完成第一个页面的搭建。
搭建好页面后,如下图所示。
二、请求获取位置
要想显示天气,首先得获取当前设备所在的位置信息,此时可以选中模拟机,点击上方导航栏依次点击Features、Location、Custom Location
接着会弹出一个可编辑框,依次输入你想要模拟机所处位置的纬度和精度并点击OK,此时就意味着模拟机身处在该坐标(经纬度可在高德地图或百度地图上选择任意地点查看)。
设置好模拟机所在位置后便可以用代码获取当前位置。
这里我们要用到第三方库CoreLocation
首先导入CoreLocation
import CoreLocation
该功能包可用于获取设备当前位置,先实例化该类型的对象并设置代理,接着调用获取位置的方法。
class ViewController: UIViewController, CLLocationManagerDelegate{//设置代理
let locationManger = CLLocationManager()//实例化对象
override func viewDidLoad(){
super.viewDidLoad()
locationManager.requestWhenInUseAuthorization()//请求授权当前位置
locationManager.desiredAccuracy = kCLLocationAccuracyThreeKilometers//设置获取的位置精准度为三公里以内
locationManager.requestLocation()//获取位置
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {//实现delegat的方法获取到位置并赋值给定义的两个量,输出验证结果是否正确
let lon = locations[0].coordinate.longitude
let lat = locations[0].coordinate.latitude
print(lon,lat)
}
func locationManager(_ manager:CLLocationManager, didFailWithError error:Error) {
print(error)//获取位置失败时调用
}
}
但是运行之后发现,程序不能正常获取位置信息,这是因为请求定位时还需要加入描述。
打开info.plist文件,点击第一行的加号,在弹出的选择框中选择Privacy-Location Always and When In Use Usage Descrip这一项,在value栏输入一行描述信息,以告知用户该操作的描述。
此时可以发现,运行后,能正确输出当前位置的坐标。
三、根据坐标发起网络请求
完成此项功能需要引入第三方库Alamofire。下面介绍如何引入这个库。
1、安装cocopods,安装好后,打开终端,cd到项目文件夹中,输入命令 pod init,会发现多了一个podfile文件。此时在github中搜索Alamofire,下滑选择installation,会看到用cocopods安装该功能包的命令,将该命令复制到podfile文件中,输入命令pod install即可导入该功能包。(第一次安装可能会比较久,因为它会把所有的第三方库索引都下载下来)异常情况:如果下载失败很可能是因为网络的限制。
一切准备就绪后,就可以正式开始进行网络请求了。
首先找到一个有天气数据的网站,通过请求该网站API得到返回的JSON数据,解析之后更新界面数据。我用的是和风天气开发平台https://dev.qweather.com 注册之后获取你的key,根据API开发文档可知所需参数。
最后根据Alamofire的使用方法可得以下代码段
AF.request("https://devapi.qweather.com/v7/weather/now?location=/(lon),/(lat)&key=自己的key").responseJSON { response in//获取JSON格式
if let data = response.value{
}
}
这样就成功获取到了天气的数据,数据结果存在data中。
四、解析天气数据
此时data中的数据为JSON格式,所以就需要再引入第三方库SwiftJSON来将JSON数据解析,SwiftJSON的引入操作可参考Alamofire的引入。最后的代码段为
AF.request("https://devapi.qweather.com/v7/weather/now?location=/(lon),/(lat)&key=自己的key").responseJSON { response in//获取JSON格式
if let data = response.value{
let weatherJSON = JSON(data)
print(weatherJSON)
}
}
此时便将数据成功转化出来,输出结果为
五、将控件内容与获取的数据联系
通过JSON的链式调用取出数据,以下是代码示例
self.label.text = weatherJSON["now","temp"].stringValue
self.iconImageView.image = UIImage(named: weatherJSON["now","icon"].stringValue)
对比JSON格式数据可发现通过这种方式可以找到now里的temp数据和now里的icon数据并赋值
六、配置第二个页面
通过Main.storyboard右上角加号拖一个新的ViewController到界面中在右侧功能区将class指定为新建的QueryViewController类,即为第二个查找指定城市天气的功能界面。
要实现点击第一个界面的搜索按钮跳转至第二个界面,按住ctrl点击搜索控件拖动至QueryViewController。页面搭建后效果如下
为实现跳转至第二个界面时显示当前城市,需在第一个界面中实现prepare方法,代码段如下
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? QueryViewController{
//判断是否跳转的是QueryViewController界面
vc.currentCity = weather.city//第二个界面的当前城市信息
}
}
此外在QueryViewController中,还有以下2个功能
1.点击返回按钮返回至第一个页面但数据并不发生改变
2.点击查询天气按钮将输入的城市传回第一个页面进行天气查询并显示
代码实现如下
protocol QueryViewControllerDelegate {//自定义协议,如果是QueryViewController类就需要实现协议内的方法
func didChangeCity(city: String)
}
class QueryViewController: UIViewController {
var currentCity = ""
var delegate: QueryViewControllerDelegate?
@IBOutlet weak var currentCityLabel: UILabel!
@IBOutlet weak var cityTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
cityTextField.becomeFirstResponder()//聚焦//cityTextField.resignFirstResponder//解除聚焦
currentCityLabel.text = currentCity
}
@IBAction func back(_ sender: Any) {//点击返回第一个界面不进行任何操作
dismiss(animated: true)
}
@IBAction func query(_ sender: Any) {//点击返回第一个界面如果输入框内容不为空调用didChangeCity方法
dismiss(animated: true)
if !cityTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty{
delegate?.didChangeCity(city: cityTextField.text!)
}
}
}
七、根据城市名发起网络请求
通过第QueryViewController返回的城市名来进行网络请求,首先由于城市名时中文字符无法作为参数所以需要通过Alamofire自带的一个parameters将城市名转化为URL编码,通过AF.request获取城市信息后,再通过城市id获取该城市的天气。
方法的实现代码如下
import Foundation
import Alamofire
import SwiftyJSON
extension ViewController: QueryViewControllerDelegate{
func didChangeCity(city: String) {
AF.request(kQWeatherCityPath,parameters: getParameters(city)).responseJSON{//查看开发文档可知当参数是城市名时,也能进行查询,但由于时中文形式所以需要通过Alamofire自带的parameters转换成URL编码
response in
if let data = response.value{
let cityJSON = JSON(data)
self.showCity(cityJSON)
let cityID = cityJSON["location",0,"id"].stringValue//通过JSON找到城市id
AF.request(kQWeatherNowPath,parameters: self.getParameters(cityID)).responseJSON { response in//用城市ID查找天气
if let data = response.value{
let weatherJSON = JSON(data)
self.showWeather(weatherJSON)
// print(weatherJSON["now","temp"])
}
}
}
}
}
}
import Foundation
import SwiftyJSON
extension ViewController{
func showWeather(_ weatherJSON: JSON){
//数据
self.weather.temp = "\(weatherJSON["now","temp"].stringValue)°"
self.weather.icon = weatherJSON["now","icon"].stringValue
//UI
self.tempLabel.text = self.weather.temp
self.iconImageView.image = UIImage(named: self.weather.icon)
}
func showCity(_ cityJSON: JSON){
//数据
weather.city = cityJSON["location",0,"adm2"].stringValue
//UI
cityLabel.text = weather.city
}
func getParameters(_ location: String) -> [String: String]{
["location": location, "key": kQWeatherWebKey]
}
}
此时不要忘记设置第一个界面的代理
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? QueryViewController{
//判断是否跳转的是QueryViewController界面
vc.currentCity = weather.city//第二个界面的当前城市信息
vc.delegate = self//设置代理
}
}
完成这些后天气的功能就基本全部实现了
八、分离化简代码
1.将多次使用的常量放在同一个文件中
2.将多次使用的方法放在同一个文件中
3.定义一个Model文件放数据变量
4.将两个控制器的代码分别放在两个文件中
如下图所示