第一篇博客

实现天气的基本思路

1、获取定位(CoreLocation获得经纬度)

2、网络请求(HTTP请求天气API)

3、下拉刷新数据

4、缓存数据

5、基本控件的实现(CollectionView、TableView等)

最终得到的天气

ios天气小部件 iphone天气小组件设置地点_json

ios天气小部件 iphone天气小组件设置地点_ios_02

获取定位

这里我们要用到第三方库CoreLocation

关于导入第三方库可以看这篇文章

首先导入CoreLocation:

import CoreLocation

CoreLocation用Location Manger获取用户的位置,在这之前要先在info.plist中加入定位描述。

ios天气小部件 iphone天气小组件设置地点_ios_03

iOS 11之后设置:

Privacy - Location When In Use Usage Description  //只允许使用应用期间APP获取地理位置

Privacy - Location Always Usage Description  //一直在后台获取更新的定位信息

我用的是第一个,因为相对于第二个,第一个会节省手机的电量

创建CLLocationManager对象并设置代理

let locationManager = CLLocationManager()//定位管理器

locationManager.delegate = self
locationManager.requestWhenInUseAuthorization()//根据info.plist来
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters//设置精度
locationManager.requestLocation()//发送授权申请
locationManager.startUpdatingLocation()//开启定位服务更新

获取地理位置和错误处理

extension MainViewController: CLLocationManagerDelegate{
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
        let location = locations[locations.count - 1]
        if(location.horizontalAccuracy > 0) {
            self.locationManager.stopUpdatingHeading()
            
            let longitude = String(location.coordinate.longitude)//获得经度
            let latitude = String(location.coordinate.latitude)//获得纬度
            //发起http请求天气
            let parms = ["lat":String(latitude),"lon":String(longitude),"appid":myKey]
            self.requestWeather(parms: parms)
        }
    }
    //错误处理
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("error")
        
    }
}

以上就可以实现定位功能

网络请求

运用HTTP请求天气站台的API,得到返回的JSON数据,解析之后更新到界面中。

我用的是https://api.openweathermap.org

⚠️在请求前要获得你的key

请求的URL:https://api.openweathermap.org/data/2.5/weather

最后得到

let weatherApi = "https://api.openweathermap.org/data/2.5/weather"
let myKey = "713e52a79d1597d9d9e1281c26feeb49"//每个人的key是不一样的,一定要获取

请求之前,先导入Alamofire、SwiftyJSON、HandyJSON等,这里要在podfile里更改

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '9.0'
inhibit_all_warnings!

target 'Weather' do
  use_frameworks!

  # Pods for Weather
  pod 'SnapKit'
  pod "ESTabBarController-swift"
  pod 'EachNavigationBar'
  pod 'DNSPageView'
  pod 'HandyJSON', :git => 'https://github.com/alibaba/HandyJSON.git', :branch => 'dev_for_swift5.0'
  pod 'SwiftyJSON'
  pod 'Alamofire'
  pod 'AlamofireImage'
  pod 'AlamofireObjectMapper'
  pod 'SwiftFCXRefresh'
end

更改后在ViewController里导入(与导入CoreLocation相似)

用第三方库Alamofire请求,responseJSON 序列化为JSON

Alamofire在默认状态下以GET(从服务器上获取数据)方式进行网络请求,其他还有POST(向服务器传达数据)、PUT、DELETE等。具体可以了解一下URLSession进行网络请求。

(结果会通过闭包的参数response返回。如果是被序列化的数据,就通过response中的 .value来获取数据)

func requestWeather(parms: Parameters){
        if getLocalData() == nil {//缓存数据
            Alamofire.request(weatherApi,parameters: parms).responseJSON { [self]
                response in
                if let json = response.value {
                    let weatherJSON = JSON(json)
                    self.Data = JSONDeserializer<WeatherData>.deserializeFrom(json: weatherJSON.description)!
                    putdata(data: Data)//缓存
                    collectionView.reloadData()//重新加载collectionView
                }
            }
        }else{
            Data = getLocalData()!
            collectionView.reloadData()
        }
    }

在Alamofire中,对请求的封装有以下几种类型

response.request//original URL request
response.response//HTTP URL response
response.data//server data
response.result//result of response serialization

如果一切正确,你会得到

{
  "name" : "Chengdu",
  "visibility" : 10000,
  "timezone" : 28800,
  "cod" : 200,
  "wind" : {
    "deg" : 0,
    "speed" : 1
  },
  "base" : "stations",
  "dt" : 1637844838,
  "sys" : {
    "type" : 1,
    "id" : 9674,
    "sunset" : 1637834593,
    "country" : "CN",
    "sunrise" : 1637797110
  },
  "coord" : {
    "lat" : 30.666699999999999,
    "lon" : 104.0667
  },
  "clouds" : {
    "all" : 40
  },
  "weather" : [
    {
      "description" : "scattered clouds",
      "id" : 802,
      "icon" : "03n",
      "main" : "Clouds"
    }
  ],
  "main" : {
    "humidity" : 58,
    "pressure" : 1023,
    "temp" : 287.08999999999997,
    "feels_like" : 286.05000000000001,
    "temp_max" : 287.08999999999997,
    "temp_min" : 287.08999999999997
  },
  "id" : 1815286
}

这只是一个例子,得到这样的数据之后是要进行解析的,解析后每一层都需要配备一个实体类。

比如此时,我得到的Model是这样的

import HandyJSON

struct WeatherData: HandyJSON {
    let coord = Coord()
    let weather = [Weather]()
    let base = ""
    var main = Main()
    let visibility = ""
    let wind = Wind()
    let cloud = Clouds()
    let dt = ""
    let sys = Sys()
    let timezone = ""
    let id = ""
    let name = ""
    let cod = ""
    
}
struct Coord: HandyJSON {
    let lon = ""
    let lat = ""
}
struct Weather: HandyJSON {
    let id = ""
    let main = ""
    let description = ""
    let icon = ""
}
struct Main: HandyJSON {
    var temp = 0
    let feels_like = 0
    let temp_min = 0
    let temp_max = 0
    let pressure = ""
    let humidity = ""
    let sea_leavel = ""
    let grnd_level = ""
}
struct Wind: HandyJSON {
    let speed = ""
    let deg = ""
    let gust = ""
}
struct Clouds: HandyJSON {
    let all = ""
}
struct Sys: HandyJSON {
    let country = ""
    let sunrise = ""
    let sunset = ""
}

这样MVC模式下的Model也创建好了

由此,网络请求功能完成!

下拉刷新数据

用第三方库SwiftFCXRefresh实现下拉刷新

先使用CocoaPods导入podfile中,在写在头文件里(与前面相同,这里不多赘述)

var headerRefreshView: FCXRefreshHeaderView?
//自动刷新功能
navigationItem.rightBarButtonItem = UIBarButtonItem.init(barButtonSystemItem: .refresh, target: self, action: #selector(autoRefresh))
@objc func autoRefresh() {
		headerRefreshView?.autoRefresh()
    self.collectionView.reloadData()
}
//下拉刷新功能
var headerRefreshView: FCXRefreshHeaderView?
func addRefreshView() {//在viewDidLoad调用
		headerRefreshView = collectionView.addFCXRefreshHeader {(refreshHeader) in
				DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) {
						refreshHeader.endRefresh()
            self.collectionView.reloadData()
        }}
}

显示效果

ios天气小部件 iphone天气小组件设置地点_ios_04

缓存数据

学习缓存数据之前我们先学习一个概念:序列化

自定义对象是无法写入到文件的,必须转换成二进制流的格式。从对象到二进制数据的过程我们称为序列化,将二进制数据还原成对象的过程,我们称为反序列化。iOS中对象序列化需要实现NSCoding类协议,实现序列化与反序列化方法

func getLocalData() -> WeatherData? {
        let path = NSHomeDirectory().appending("/Documents/user.plist")//文件位置
        let data = NSArray(contentsOfFile: path)//文件内容
        let dic = data?[0] as? NSDictionary//转成NSDictionary
        let model = WeatherData.deserialize(from: dic, designatedPath: "")//反序列化
        return model
    }
    
    func putdata(data: WeatherData) {
        let dic = NSDictionary(dictionary: data.toJSON()!)
        let path = NSHomeDirectory().appending("/Documents/user.plist")//文件位置
        let arr = NSMutableArray() //可变数组
        arr.add(dic) //增加元素
        arr.write(toFile: path, atomically: true)//将数组写入plist文件
    }

注意:网络请求之前要判断本地数据是否为nil,如果是则进行网络请求

网络请求后要重写本地数据

func requestWeather(parms: Parameters){
  			//判断本地数据
        if getLocalData() == nil {
            Alamofire.request(weatherApi,parameters: parms).responseJSON { [self]
                response in
                if let json = response.value {
                    let weatherJSON = JSON(json)
                    self.Data = JSONDeserializer<WeatherData>.deserializeFrom(json: weatherJSON.description)!
                    //重写本地数据
                    putdata(data: Data)
                    collectionView.reloadData()
                }
            }
        }else{
            Data = getLocalData()!
            collectionView.reloadData()
        }
    }

基本控件的实现

基本控件就是一些UICollectionView、UITableView、Label、UIImageView等

在主界面运用UICollectionView来将搜索、温度、详情分成三个cell来写,点进搜索后运用了UITableView来选择城市

CollectionViewLayout里有函数可以设置cell的大小和间距

设置cell的大小

func collectionView(_ collectionView: UICollectionView,layout collectonViewLayout: UICollectionViewLayout,sizeForItemAt indexPath: IndexPath) -> CGSize{
        return CGSize(width: 100, height: 100)
    }

设置cell的间距

横向  minimumLineSpacing
纵向  minimumInteritemSpacing

举个例子当我 layout.scrollDirection = .horizontal(横向)时

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return 24
    }

运用SnapKit画UI,很方便

举个例子

nameLabel.snp.makeConstraints { make in
	make.top.equalToSuperview().offset(80)
  make.left.equalToSuperview().offset(40)
  make.width.equalTo(200)
  make.height.equalTo(50)
}
        
cloudLabel.snp.makeConstraints { make in
	make.top.equalTo(nameLabel.snp.bottom).offset(20)
	make.left.equalToSuperview().offset(40)
  make.right.equalToSuperview().offset(0)
  make.height.equalTo(30)
}

以上就是我这两个月的学习总结,当然不止这些还有一些动画啊模糊效果这些后续会有博客介绍❤️