用Swift实现一款天气预报APP(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

上篇中主要讲了界面的一些内容,这篇主要讨论网络请求,获得天气的数据。具体的说是HTTP请求天气站点的API,得到返回的JSON数据。解析这些数据,并更新到界面内容中。 让用户知道当前的和之后几个小时的天气状况。

发起HTTP请求主要用到的是SDK的NSURLSession这个类,使用这个类对象可以创建请求任务并在这个任务中处理请求之后由服务器返回的JSON数据。在NSURLSession之前主要用到的是NSURLConnection。这两个类比较类似。只是在NSURLSession中增加了后台执行的请求。发起网络请求的时候,使用NSURLSession创建对应的NSURLSessionTask,并由这个Task请求服务器和处理返回的数据。

下面大体的看看我们怎么做HTTP请求的。本文将主要叙述如何发起HTTP请求。先讲讲使用最基本的iOS的SDK发请求,然后叙述如何用现在比较流向的AFNetworking框架请求。或许你也听说过一个叫做ASIHttpRequest的框架,但是这个已经很久没有人维护了。所以,这里就不再提及。

使用iOS SDK发起HTTP网络请求:

1. 准备访问服务器的NSURL对象。这个对象需要一个url字符串,比如百度的地址字符串就是“http://www.baidu.com”,我们这里需要一个指向天气服务器的字符串。

var weatherUrl = "http://api.openweathermap.org/data/2.5/forecast?lat=\(latitude)&lon=\(longitude)"
var url = NSURL(string: weatherUrl)

第一句的问好后面的部分?lat=\(latitude)&lon=\(longitude)是为url指定用户当前的经纬度。之后根据这个url字符串生成NSURL对象。

2. 创建NSURLSession对象。NSURLSession有一个类方法创建实例。

self.urlSession = NSURLSession.sharedSession()

一般用到shareXXX的方式命名的方法是一个单例方法。也就是这个方法在被调用的时候会判断需要的实例是否已经创建,如果是的话返回创建好的实例,如果没有创建则初始化一个并保存起来以备下次使用。关于使用Swift实现单例模式,请参考这里

3. 创建NSURLSessionDataTask,并设置好如何处理请求返回的数据。然后开始HTTP请求。

复制代码
var task = self.urlSession.dataTaskWithURL(url!, completionHandler: {(data:NSData!, response: NSURLResponse!, error: NSError!) in
        if error != nil {
            println("http request error \(error)")
            return
        }
        println("\(response)")
        var httpResponse = response as NSHTTPURLResponse
        var statusCode: NSInteger = httpResponse.statusCode
        println("status code: \(statusCode)")
            
        var error: NSError?
        var jsonDictionary = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.AllowFragments, error: &error) as NSDictionary
        if error != nil {
            println("json error")
            return
        }
            
        println("json \(jsonDictionary)")
        self.jsonLabel.text = jsonDictionary.description
    })
    task.resume()
复制代码

self.urlSession.dataTaskWithURL这个方法创建了一个DataTask。completionHandler后面的就是指定的处理返回数据的方法。这里使用了Swit的闭包。闭包的语法可以简单的概括为{(参数列表。。。)->闭包的返回类型 in 功能代码在这里}具体的参考上面的代码示例。那么具体的,我们应该如何处理返回的数据呢。第一步,先查看返回的错误error是否为空。如果为空就是没有错,否则,就是有错了。这个时候就可以提示用户后直接return,不再处理后面的代码了。

下面就是检查response的statusCode。状态码最直观的就是大家都见过的404,虾米都木有找到的时候的提示。如果是200,那么就是请求服务器成功。否则,也可以提示用户后返回了。

最后就是解析用户数据了。首先需要把服务器返回的JSON格式的数据转换为Swift可以直接访问的NSDictionary。记住,这里是NSDictionary不是Swift基础数据类型中的泛型Dictionary<KeyType, ValueType>。服务器的JSON数据转换成NSDictionary后就可以取出需要的数据并更新到主界面上了。

这里你会发现很多的代码调用都是通过NSError的实例是否为空判断某函数的执行是否有错误发生的。Swift没有try-catch的异常处理模式。只有这样的error的方式。这个大家需要习惯。用这种方式处理错误是为了去掉代码的二意性。有其他语言编程经理的都知道,有时候就用try-catch来做代码的某些判断了。这是不对的。

 最后调用task的resume方法开始HTTP请求。

 前文已经简单的提到过定位的功能。本文在这里之前都在讨论HTTP请求的功能。如前面提到的,请求天气数据到时候需要用到经纬度的数据作为url参数。所以HTTP请求只能在定位成功获取到用户当前的经纬度之后进行。所以,在代码实现的时候,网络请求在Location Manager的定位成功的代理方法中发起。

复制代码
func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!){
        println("get location")
        var location:CLLocation = locations[locations.count-1] as CLLocation
        
        if (location.horizontalAccuracy > 0) {
            self.locationManager.stopUpdatingLocation()
            println(location.coordinate)

            self.textLabel.text = "latitude \(location.coordinate.latitude) longitude \(location.coordinate.longitude)"
            
            // 在这里发起HTTP请求
            self.updateWeatherWith(location.coordinate.latitude, longitude: location.coordinate.longitude)
        }
    }
复制代码

 

到此为止,从获取用户位置到使用用户的经纬度数据请求天气服务器获取天气的JSON数据的功能都已经衔接在一起了。

那么,我们来讨论一下如何使用AFNetworking这个框架(framework)。在这之前,用户需要配置cocoaPods。具体的步骤可以参考这里。这里必须吐槽一下,Ruby什么的编程之类的网站多要墙真是不可理喻啊。配置好之后,亲,你一定要点击的时候workspace那个后缀的文件,不是项目文件了。否则会出错。

要使用AFNetworking框架就涉及到一个Objective-C和Swift交互的问题了。

let manager = AFHTTPRequestOperationManager()

这行代码直接编译不通过。。。稍微深究机会发现,在Swift中没有办法直接使用OC(Objective-C)的代码。翻翻项目,找到SwiftWeather-Bridging-Header.h头文件,然后在里面添加对于AFNetworking框架的引用。

#import <AFNetworking/AFNetworking.h>

添加后,编译你的项目。上面那行出错的代码就可以用了。

使用AFNetworking框架确实会很方便。不用像使用NSURLSession里那样写那么多的代码。这个通过一个简单的感官比较就会得出结论。先在上AFNetworking的HTTP请求代码。

复制代码
let manager = AFHTTPRequestOperationManager()
        
    let url = "http://api.openweathermap.org/data/2.5/forecast"
    println(url)
        
    let params = ["lat":latitude, "lon":longitude, "cnt":0]
    println(params)
        
    manager.GET(url,
        parameters: params,
        success: { (operation: AFHTTPRequestOperation!,
            responseObject: AnyObject!) in
            //println("JSON: " + responseObject.description!)
                
            self.updateUISuccess(responseObject as NSDictionary!)
        },
        failure: { (operation: AFHTTPRequestOperation!,
            error: NSError!) in
            println("Error: " + error.localizedDescription)
                
            self.loading.text = "Internet appears down!"
        })
复制代码

初始化一个AFHTTPRequestOperationManager来处理请求和数据返回等的处理,一个类就够了。不用task什么的了。指定要访问的url字符串,这里是字符串也不需要NSURL的实例了。然后把需要给url字符串添加的参数放在一个Dictionary<String, String>泛型字典中。然后用manager发出HTTP请求,并指定了请求的方式为GET,函数的名字就是HTTP请求的方式。HTTP请求还有除GET之外的很多中,其中最常用的是POST。然后可以看到GET方法中的sucess和failure,都分别是在指定请求成功的处理代码和失败的处理代码。

请求数据不是总能成功。这在代码中也有体现。但是不成功的数据请求并不只是请求不到数据,比如在网络不通的时候。还包括请求到了数据,但是数据表明这个请求是错误的。所以,在网络连接失败而造成的网络请求失败时提醒用户“Internet apears down”。在数据解析后发现服务器返回数据提示说数据错误,这个时候也要提醒用户错误。这里只是点到,不做其他处理。读者在实际的开发中需要注意这一点。 

数据请求完成后,调用方法updateUISuccess把数据显示在界面元素中。从上到下,依次是用户所在地(文字),天气(图片),温度(文字)。然后在下面,从左到右,依次显示这一天中其他几个小时 的天气预报。

复制代码
    func updateUISuccess(jsonResult: NSDictionary) {
        self.loading.text = nil
        self.loadingIndicator.hidden = true
        self.loadingIndicator.stopAnimating()
        
        if let tempResult = ((jsonResult["list"]? as NSArray)[0]["main"] as NSDictionary)["temp"] as? Double {
            
            // If we can get the temperature from JSON correctly, we assume the rest of JSON is correct.
            var temperature: Double
            var cntry: String
            cntry = ""
            if let city = (jsonResult["city"]? as? NSDictionary) {
                if let country = (city["country"] as? String) {
                    cntry = country
                    if (country == "US") {
                        // Convert temperature to Fahrenheit if user is within the US
                        temperature = round(((tempResult - 273.15) * 1.8) + 32)
                    }
                    else {
                        // Otherwise, convert temperature to Celsius
                        temperature = round(tempResult - 273.15)
                    }
                    
                    // FIXED: Is it a bug of Xcode 6? can not set the font size in IB.
                    //self.temperature.font = UIFont.boldSystemFontOfSize(60)
                    self.temperature.text = "\(temperature)°"
                }
                
                if let name = (city["name"] as? String) {
                    self.location.font = UIFont.boldSystemFontOfSize(25)
                    self.location.text = name
                }
            }
            
            if let weatherArray = (jsonResult["list"]? as? NSArray) {
                for index in 0...4 {
                    if let perTime = (weatherArray[index] as? NSDictionary) {
                        if let main = (perTime["main"]? as? NSDictionary) {
                            var temp = (main["temp"] as Double)
                            if (cntry == "US") {
                                // Convert temperature to Fahrenheit if user is within the US
                                temperature = round(((temp - 273.15) * 1.8) + 32)
                            }
                            else {
                                // Otherwise, convert temperature to Celsius
                                temperature = round(temp - 273.15)
                            }
                            
                            //FIXED: Is it a bug of Xcode 6? can not set the font size in IB.
                            //self.temperature.font = UIFont.boldSystemFontOfSize(60)
                            if (index == 1) {
                                self.temp1.text = "\(temperature)°"
                            }
                            if (index == 2) {
                                self.temp2.text = "\(temperature)°"
                            }
                            if (index == 3) {
                                self.temp3.text = "\(temperature)°"
                            }
                            if (index == 4) {
                                self.temp4.text = "\(temperature)°"
                            }
                        }
                        var dateFormatter = NSDateFormatter()
                        dateFormatter.dateFormat = "HH:mm"
                        if let date = (perTime["dt"]? as? Double) {
                            let thisDate = NSDate(timeIntervalSince1970: date)
                            let forecastTime = dateFormatter.stringFromDate(thisDate)
                            if (index==1) {
                                self.time1.text = forecastTime
                            }
                            if (index==2) {
                                self.time2.text = forecastTime
                            }
                            if (index==3) {
                                self.time3.text = forecastTime
                            }
                            if (index==4) {
                                self.time4.text = forecastTime
                            }
                        }
                        if let weather = (perTime["weather"]? as? NSArray) {
                            var condition = (weather[0] as NSDictionary)["id"] as Int
                            var icon = (weather[0] as NSDictionary)["icon"] as String
                            var nightTime = false
                            if icon.rangeOfString("n") != nil{
                                nightTime = true
                            }
                            self.updateWeatherIcon(condition, nightTime: nightTime, index: index)
                            if (index == 4) {
                                return
                            }
                        }
                    }
                }
            }
        }
        self.loading.text = "Weather info is not available!"
    }
复制代码

然后,根据不同的解析结果,跟新当前的和后面几个小时的天气调用方法updateWeatherIconupdatePictures更新天气图片(白天、晚上、天气)。示例工程中会有详细的实现。这里略去不提。

这个时候,运行APP之后已经可以看到天气预报的主界面了。

 

欢迎加群互相学习,共同进步。QQ群:iOS: 58099570 | Android: 330987132 | Go:217696290 | Python:336880185 | 做人要厚道,转载请注明出处!http://www.cnblogs.com/sunshine-anycall/p/4149052.html
相关文章
|
4月前
|
消息中间件 Java
【实战揭秘】如何运用Java发布-订阅模式,打造高效响应式天气预报App?
【8月更文挑战第30天】发布-订阅模式是一种消息通信模型,发送者将消息发布到公共队列,接收者自行订阅并处理。此模式降低了对象间的耦合度,使系统更灵活、可扩展。例如,在天气预报应用中,`WeatherEventPublisher` 类作为发布者收集天气数据并通知订阅者(如 `TemperatureDisplay` 和 `HumidityDisplay`),实现组件间的解耦和动态更新。这种方式适用于事件驱动的应用,提高了系统的扩展性和可维护性。
74 2
|
6月前
|
数据可视化 数据处理 Swift
Swift开发——简单App设计
SwiftUI教程概述:简化App设计,通过代码展示了如何创建一个计算两个数之和的界面。工程`MyCh0902`包含`ContentView.swift`,其中定义了`ContentView`和`MyView`结构体。`MyView`负责界面布局,使用`VStack`和`HStack`组织元素,如`TextField`和`Button`。点击`Button`调用`calc`方法处理输入并更新结果。界面设计可在Xcode的Inspector窗口中可视化配置。推荐将界面逻辑移到单独的`MyView.swift`文件中以清晰分离视图设计。
255 1
Swift开发——简单App设计
|
5月前
|
JSON API 数据格式
App Inventor 2 天气预报App开发 - 第三方API接入的通用方法
通过调用第三方天气api,填入必要的参数,通过Web客户端请求url。返回json格式的数据结果,使用AppInventor2解析json结果,显示到App上即可。
148 5
|
7月前
|
存储 数据可视化 API
天气预报app(安卓系统)
天气预报app(安卓系统)
|
JavaScript 前端开发 PHP
用swift开发ios移动端app应用初体验
直接跟着 apple 官方的 SwiftUI 教程跑的,写惯了 javascript 奔放的代码,很多语法理解起来还是有点费劲
103 1
|
API
利用QT实现主界面APP应用开发之经典
利用QT实现主界面APP应用开发之经典
315 1
利用QT实现主界面APP应用开发之经典
|
Linux iOS开发 开发者
实现在windows、linux下上传ios app到App Store
实现在windows、linux下上传ios app到App Store
实现在windows、linux下上传ios app到App Store
|
JSON JavaScript 安全
基于Windows微信实现实时收发微信消息App
基于Windows微信实现实时收发微信消息App
1422 0
基于Windows微信实现实时收发微信消息App
|
监控 前端开发 Java
Android自定义控件(十)——SurfaceView实战实现天气APP背景移动效果
Android自定义控件(十)——SurfaceView实战实现天气APP背景移动效果
459 0
|
XML 编解码 前端开发
应用层安卓app的基本原理与实现之经典
今天主要和大家聊一聊,如何利用Android Studio实现一个app,虽然说可以利用QT来制作app,但是和Android Studio还是差很多。接下来主要是来熟悉一下其中的操作步骤。
300 0
应用层安卓app的基本原理与实现之经典