工程结构如下:

  1. ViewController :捕捉用户手势,发指令给华为盒子。
  2. WeatherInfo :从网络服务获取空气质量指数和天气预报。
  3. Settings.bundle :app的一些设置放到iOS的设置里。

重要代码记录如下:

ViewController.h

//
//  ViewController.h
//  HuaWeiRemoteCtl
//
//  Created by jia xiaodong on 12/16/15.
//  Copyright (c) 2015 homemade. All rights reserved.
//#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>#import "WeatherInfo.h"enum NetworkStatus
{NETWORK_NOT_REACHABLE  = 0,NETWORK_THRU_WIFI      = 1, // network traffic is through local WifiNETWORK_THRU_WWAN     = 2, // 3G, GPRS, Edge or other cellular data network
};@interface ViewController : UIViewController<NSURLConnectionDelegate>
{// These magic numbers are extracted from http://health.vmall.com/mediaQ/controller.jspenum ActionCode{ACTION_OK       = 0,ACTION_LEFT        = 1,ACTION_DOWN        = 2,ACTION_RIGHT   = 3,ACTION_UP      = 4,ACTION_BACK        = 5,ACTION_HOME        = 6,ACTION_MENU        = 7,ACTION_POWER   = 8,ACTION_VOL_UP  = 9,ACTION_VOL_DOWN    = 10};NSString* mBoxIPAddress;//! user can pan his finger to four directionsenum ActionDirection{DIRECTION_INVALID,DIRECTION_LEFT  = ACTION_LEFT,DIRECTION_DOWN   = ACTION_DOWN,DIRECTION_RIGHT  = ACTION_RIGHT,DIRECTION_UP    = ACTION_UP} mCurrDirection, mPrevDirection;//! process long press gesture when finger panningNSTimer* mRepeatDelayer;NSTimer* mActionRepeater;//! Single-tap | Double-tap OKBOOL mIsDoubleTapOK;UITapGestureRecognizer* mTapGesture;BOOL mIsShowingForecastInfo;BOOL mIsShowingTodayDetails;LocationID mGeoLocation;//! monitor device's network traffic pathenum NetworkStatus mNetworkStatus;//! weather infoUILabel* mWeatherInfoBoard;UIScrollView* mScrollLabel;    // text's too long, so need a scroll-effectUIButton* mToggleInfoButton;WeatherFullReport* mWeatherInfo;NSMutableString* mLabelText;UIActivityIndicatorView* mBusyCircle;//BOOL mAqiReady, mDetailReady;
}@property (nonatomic, copy) NSString* BoxIPAddress;
@property (atomic, assign) enum ActionDirection CurrentDir;
@property (atomic, assign) enum ActionDirection PreviousDir;- (IBAction)BtnVolDown:(UIButton *)sender;
- (IBAction)BtnVolUp:(UIButton *)sender;- (IBAction)power:(id)sender;
- (IBAction)home:(id)sender;
- (IBAction)menu:(id)sender;
- (IBAction)back:(id)sender;//! all remote control's functionalities
- (void)performAction:(enum ActionCode)code;//! delegate methods
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;//!
- (void)registerTapGesture;//!
- (void)resetPanTimer;+ (BOOL)isValidIPv4Address:(NSString*)ip;//! //! monitor device's network traffic path
- (void) startMonitorNetwork;
- (void) stopMonitorNetwork;
- (void) parseReachabilityFlags: (SCNetworkReachabilityFlags)flags;//!
- (void) toggleWeatherButton;@end

ViewController.mm

//
//  ViewController.m
//  HuaWeiRemoteCtl
//
//  Created by jia xiaodong on 12/16/15.
//  Copyright (c) 2015 homemade. All rights reserved.
//#import "ViewController.h"
#include <memory>
#include <netinet/in.h>NSString* KEY_BOX_IP_ADDRESS = @"box_ip_address";
NSString* KEY_DOUBLE_TAP_OK  = @"double_tap_ok";
NSString* DEFAULT_IP_ADDRESS = @"192.168.1.106";    // default box IPv4 address
NSString* KEY_FORECAST_INFO  = @"forecast_info";
NSString* KEY_TODAY_DETAILS  = @"detailed_forecast";
NSString* KEY_ALARM_INFO     = @"alarm_info";
NSString* KEY_LOCATION       = @"location_setting";static SCNetworkReachabilityRef sReachabilityRef;//! callback which can receive device's event of network path changing
void ReachabilityCallback(SCNetworkReachabilityRef target, SCNetworkReachabilityFlags flags, void* info)
{ViewController* checker = static_cast<ViewController *>(info);[checker parseReachabilityFlags:flags];
}@interface ViewController ()@end@implementation ViewController@synthesize BoxIPAddress = mBoxIPAddress;
@synthesize CurrentDir   = mCurrDirection;
@synthesize PreviousDir  = mPrevDirection;#pragma mark -
#pragma mark variable initialization- (void)viewDidLoad
{[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.mBoxIPAddress = DEFAULT_IP_ADDRESS;mIsDoubleTapOK = YES;mCurrDirection = mPrevDirection = DIRECTION_INVALID;mRepeatDelayer = nil;mActionRepeater = nil;mTapGesture = nil;// double tap: button OK[self registerTapGesture];// pinch open: volume up; pinch close: volume downUIPinchGestureRecognizer* pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];[self.view addGestureRecognizer:pinch];[pinch release];// pan to up, down, left and right directionUIPanGestureRecognizer* pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];[self.view addGestureRecognizer:pan];[pan release];//! show AQI at screen bottomconst int PADDING = 5, HEIGHT = 30;CGRect rect = [self.view bounds];mToggleInfoButton = [[UIButton alloc] initWithFrame:CGRectMake(PADDING, rect.size.height-PADDING-HEIGHT, rect.size.width-PADDING*2, HEIGHT)];[mToggleInfoButton setBackgroundColor:[UIColor grayColor]];[mToggleInfoButton setTitle:@"查看天气" forState:UIControlStateNormal];[mToggleInfoButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];[mToggleInfoButton setTitleColor:[UIColor darkGrayColor] forState:UIControlStateHighlighted];[mToggleInfoButton addTarget:self action:@selector(toggleWeatherButton) forControlEvents:UIControlEventTouchUpInside];[self.view addSubview:mToggleInfoButton];mWeatherInfoBoard = nil;mScrollLabel = nil;mLabelText = nil;mBusyCircle = nil;mWeatherInfo = nil;[self getCurrentNetworkPath];[self startMonitorNetwork];
}- (void)didReceiveMemoryWarning
{[super didReceiveMemoryWarning];// Dispose of any resources that can be recreated.
}#pragma mark -
#pragma mark user action feedback- (IBAction)BtnVolDown:(UIButton *)sender {[self performAction:ACTION_VOL_DOWN];
}- (IBAction)BtnVolUp:(UIButton *)sender {[self performAction:ACTION_VOL_UP];
}- (IBAction)power:(id)sender {[self performAction:ACTION_POWER];
}- (IBAction)home:(id)sender {[self performAction:ACTION_HOME];
}- (IBAction)menu:(id)sender {[self performAction:ACTION_MENU];
}- (IBAction)back:(id)sender {[self performAction:ACTION_BACK];
}- (void)performAction:(enum ActionCode)code {// all remote control functionalities must work under same local network (WiFi).if (mNetworkStatus != NETWORK_THRU_WIFI){return;}dispatch_async(dispatch_get_main_queue(), ^{NSString* urlTemplate = [[NSString alloc] initWithFormat:@"http://%@:7766/remote?key=%d", self.BoxIPAddress, code];NSLog(@"[HuaWei] request: %@", urlTemplate);NSURL* url = [[NSURL alloc] initWithString:urlTemplate];NSURLRequest* request = [[NSURLRequest alloc] initWithURL:urlcachePolicy:NSURLRequestReloadIgnoringCacheDatatimeoutInterval:1.0];NSURLConnection* connection = [[NSURLConnection alloc] initWithRequest:requestdelegate:selfstartImmediately:YES];[connection release];[request release];[url release];[urlTemplate release];});
}- (void)handlePan:(UIPanGestureRecognizer*)gesture {if (gesture.state == UIGestureRecognizerStateBegan){mCurrDirection = mPrevDirection = DIRECTION_INVALID;}else if (gesture.state == UIGestureRecognizerStateChanged){CGPoint pt = [gesture translationInView:self.view];float xabs = fabsf(pt.x);float yabs = fabsf(pt.y);if (xabs > yabs){mCurrDirection = pt.x > 0 ? DIRECTION_RIGHT: DIRECTION_LEFT;}else if (xabs < yabs){mCurrDirection = pt.y > 0 ? DIRECTION_DOWN : DIRECTION_UP;}if (mPrevDirection != mCurrDirection){//! Firstly, respond to user's input[self performAction:(enum ActionCode)mCurrDirection];//! Secondly, begin to monitor user's long press//! If user keeps initial direction for more than 0.5 second, accelerate that input[self resetPanTimer];mRepeatDelayer = [NSTimer scheduledTimerWithTimeInterval:0.5target:selfselector:@selector(startRepeater)userInfo:nilrepeats:NO];    // no repeat: run-loop won't keep referencemPrevDirection = mCurrDirection;}}else if (gesture.state == UIGestureRecognizerStateEnded){mCurrDirection = mPrevDirection = DIRECTION_INVALID;[self resetPanTimer];}
}- (void)startRepeater
{mRepeatDelayer = nil; // no repeat: run-loop won't keep reference. so no need to invalidate itmActionRepeater = [NSTimer scheduledTimerWithTimeInterval:0.2target:selfselector:@selector(handleLongPress:)userInfo:nilrepeats:YES];
}- (void)handleTap:(UITapGestureRecognizer*)gesture {CGPoint pt = [gesture locationInView:self.view];if (pt.y > 170)    // upper screen is full of buttons{[self performAction:ACTION_OK];}
}- (void)handlePinch:(UIPinchGestureRecognizer*)gesture {if (gesture.state == UIGestureRecognizerStateEnded){[self performAction:(gesture.scale > 1.0f ? ACTION_VOL_UP : ACTION_VOL_DOWN)];}
}- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {/*NSString* text = [[NSString alloc] initWithFormat:@"[%@] %@", mBoxIPAddress, [error localizedDescription]];UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"Hua Wei Box"message:textdelegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil, nil];[alert show];[alert release];[text release];*/
}- (void)handleLongPress:(NSTimer*) timer {enum ActionCode action = (enum ActionCode)self.CurrentDir;[self performAction:action];
}- (void)resetPanTimer {[mRepeatDelayer invalidate]; // Run-loop will release timer's referencemRepeatDelayer = nil;[mActionRepeater invalidate];mActionRepeater = nil;
}- (void)registerTapGesture {NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];//! [1] Box IP addressNSUserDefaults *config = [NSUserDefaults standardUserDefaults];NSString* ip = [config stringForKey:KEY_BOX_IP_ADDRESS];if ([ViewController isValidIPv4Address:ip] && [ip compare:mBoxIPAddress] != NSOrderedSame){mBoxIPAddress = ip;}//! [2] Is double-tap / single-tap effectiveBOOL isDoubleTapOK = [config boolForKey:KEY_DOUBLE_TAP_OK];if (isDoubleTapOK != mIsDoubleTapOK || mTapGesture == nil){mIsDoubleTapOK = isDoubleTapOK;[self.view removeGestureRecognizer:mTapGesture];[mTapGesture release];mTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTap:)];[mTapGesture setNumberOfTapsRequired:(mIsDoubleTapOK ? 2 : 1)];[self.view addGestureRecognizer:mTapGesture];}mIsShowingForecastInfo = [config boolForKey:KEY_FORECAST_INFO];mIsShowingTodayDetails = [config boolForKey:KEY_TODAY_DETAILS];mGeoLocation = static_cast<LocationID>([config integerForKey:KEY_LOCATION]);[pool release];
}+ (BOOL)isValidIPv4Address:(NSString*)ip {NSString * regex = @"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.""([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.""([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.""([01]?\\d\\d?|2[0-4]\\d|25[0-5])$";NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", regex];return [predicate evaluateWithObject:ip];
}#pragma mark -
#pragma mark network status detection- (void) startMonitorNetwork {if (sReachabilityRef){SCNetworkReachabilityContext context = {0, self, NULL, NULL, NULL};if (SCNetworkReachabilitySetCallback(sReachabilityRef, ReachabilityCallback, &context)){SCNetworkReachabilityScheduleWithRunLoop(sReachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);}}
}- (void) getCurrentNetworkPath {struct sockaddr_in zeroAddr;bzero(&zeroAddr, sizeof(zeroAddr));zeroAddr.sin_len = sizeof(zeroAddr);zeroAddr.sin_family = AF_INET;sReachabilityRef =  SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *) &zeroAddr);SCNetworkReachabilityFlags flags;SCNetworkReachabilityGetFlags(sReachabilityRef, &flags);[self parseReachabilityFlags:flags];
}- (void) stopMonitorNetwork {if (sReachabilityRef){SCNetworkReachabilityUnscheduleFromRunLoop(sReachabilityRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);}
}- (void) parseReachabilityFlags: (SCNetworkReachabilityFlags)flags
{if (flags & kSCNetworkFlagsReachable){mNetworkStatus = (flags & kSCNetworkReachabilityFlagsIsWWAN) ? NETWORK_THRU_WWAN : NETWORK_THRU_WIFI;}else{mNetworkStatus = NETWORK_NOT_REACHABLE;}
}#pragma mark -
#pragma mark UI of weather info- (void) toggleWeatherButton
{NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool(pool, [](NSAutoreleasePool* p) {[p release];});if (mWeatherInfo == nil){if (mNetworkStatus == NETWORK_NOT_REACHABLE){UIAlertView* alert = [[UIAlertView alloc] initWithTitle:@"天气"message:@"没网,请先联网!"delegate:nilcancelButtonTitle:@"OK"otherButtonTitles:nil];[alert show];[alert release];return;}mLabelText = [[NSMutableString alloc] init];[self startActivityAnimation];[mToggleInfoButton setTitle:@"关闭" forState:UIControlStateNormal];mWeatherInfo = [[WeatherFullReport alloc] initWithLocation:mGeoLocation];[self checkWeatherSetting];[mWeatherInfo queryWithCompletionHandler:^(BOOL aqiReady, BOOL otherReady) {if (aqiReady) {mAqiReady = TRUE;}if (otherReady) {mDetailReady = TRUE;}[mLabelText setString:@""];[self organizeTextForPresentation];dispatch_async(dispatch_get_main_queue(), ^{if (mScrollLabel != nil) {[self destroyScrollLabel];}[self createScrollLable];[mWeatherInfoBoard setText:mLabelText];if (mAqiReady && mDetailReady) {[self stopActivityAnimation];mAqiReady = mDetailReady = FALSE; // reset}});}];}else{[self destroyScrollLabel];[mWeatherInfo release]; mWeatherInfo = nil;[mLabelText release]; mLabelText = nil;[self stopActivityAnimation];[mToggleInfoButton setTitle:@"查看天气" forState:UIControlStateNormal];}
}- (void)checkWeatherSetting
{mWeatherInfo.details.options = WEATHER_NOW;if (mIsShowingForecastInfo) {mWeatherInfo.details.options |= WEATHER_FORECAST;}if (mIsShowingTodayDetails) {mWeatherInfo.details.options |= WEATHER_DETAIL_FORECAST;}
}- (void) createScrollLable
{CGRect rect = [self.view bounds];CGFloat PADDING = 5;CGFloat X = PADDING, Y = PADDING + 170;CGFloat SCROLL_WIDTH = rect.size.width-2*PADDING;CGFloat SCROLL_HEIGHT = rect.size.height-Y-50;mScrollLabel = [[UIScrollView alloc] initWithFrame:CGRectMake(X, Y, SCROLL_WIDTH, SCROLL_HEIGHT)];UIFont* font = [UIFont systemFontOfSize:14];CGSize contentSize = [mLabelText sizeWithFont:font constrainedToSize:CGSizeMake(SCROLL_WIDTH, 2048)];mWeatherInfoBoard = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, contentSize.width, contentSize.height)];mWeatherInfoBoard.font = font;mWeatherInfoBoard.lineBreakMode = NSLineBreakByWordWrapping;mWeatherInfoBoard.numberOfLines = 0;mWeatherInfoBoard.backgroundColor = [UIColor clearColor];mScrollLabel.contentSize = mWeatherInfoBoard.frame.size;[mScrollLabel addSubview:mWeatherInfoBoard];[self.view addSubview:mScrollLabel];
}- (void) destroyScrollLabel
{[mScrollLabel removeFromSuperview];[mWeatherInfoBoard removeFromSuperview];[mWeatherInfoBoard release]; mWeatherInfoBoard = nil;[mScrollLabel release]; mScrollLabel = nil;
}- (void) organizeTextForPresentation
{if ([@"" compare:mWeatherInfo.aqi.result] != NSOrderedSame) {[mLabelText appendFormat:@"%@", mWeatherInfo.aqi.result];}if ([@"" compare:mWeatherInfo.details.result] != NSOrderedSame) {[mLabelText appendFormat:@"\n%@", mWeatherInfo.details.result];}
}- (void) startActivityAnimation
{CGRect screen = self.view.bounds;mBusyCircle = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(screen.size.width/2, screen.size.height/2, 10, 10)];[mBusyCircle setColor:[UIColor grayColor]];[self.view addSubview:mBusyCircle];[mBusyCircle startAnimating];
}- (void) stopActivityAnimation
{if (mBusyCircle) {[mBusyCircle stopAnimating];[mBusyCircle removeFromSuperview];[mBusyCircle release]; mBusyCircle = nil;}
}@end

WeatherInfo.h

//
//  WeatherInfo.h
//  HuaWeiRemoteCtl
//
//  Created by jia xiaodong on 5/2/16.
//  Modified on 2/7/17
//typedef NS_ENUM(NSUInteger, LocationID)
{Beijing_GuoFengMeiLun,Beijing_ZhongGuanCun,ShiJiaZhuang_WorkerHospital,QinHuangDao_LuLong
};#pragma mark -
#pragma mark a common interface for general purpose Weather info provider
@protocol WeatherServiceProvider <NSObject>
@required
- (void)launchQuery:(LocationID)location completionHander:(void(^)(BOOL success))handler;
+ (NSString*)StringFromId:(LocationID)loc;
@end#pragma mark -
#pragma mark Air Quality Index from aqicn.org
/** http://aqicn.org/map/beijing/cn/A professional AQI website, free of charge, gobally covered.
*/
@interface AirQualityIndex : NSObject <WeatherServiceProvider>
{NSString* mPM2_5;
}
@property (readonly) NSString* result;
- (void)launchQuery:(LocationID)location completionHander:(void(^)(BOOL success))handler;
+ (NSString*)StringFromId:(LocationID)loc;
@end#pragma mark -
#pragma mark a base class for detailed weather info
typedef NS_OPTIONS(NSUInteger, WeatherOption)
{WEATHER_NOW             = 1<<0, // today's weatherWEATHER_FORECAST        = 1<<1, // weather forecast for several days of futureWEATHER_DETAIL_FORECAST = 1<<2, // detailed forecast (for today only, hourly forecast)WEATHER_ALARM           = 1<<3  // not implemented
};@interface DetailedWeatherInfo : NSObject <WeatherServiceProvider>
{WeatherOption mWeatherOptions;NSString* mResult;
}
@property WeatherOption options;
@property (nonatomic, copy) NSString* result;
@end#pragma mark -
#pragma mark weather service from www.heweather.com (free for personal use)
/**Registered as a free user by an email address, you can get 3000 queries per day.Personal key: http://console.heweather.com/my/serviceDocs: http://docs.heweather.com/224291City list: http://docs.heweather.com/224293*/
@interface HeWeatherInfoNode : NSObject
{NSString* date;NSString* astro;            // rise/set time of sun and moonNSString* condition;        // sunny, cloudy, rainny, ...NSString* temperature;      // Celsius degreeNSString* humidity;         // relative humidity (%)NSString* probability;      // probability of precipitationNSString* precipitation;    // amount of precipitation (mm)NSString* pressure;         // atmospheric pressure (mmHg)NSString* uv;               // ultraviolet-ray radiation degreeNSString* visibility;       // kmNSString* wind;             // wind
}
@property (nonatomic, copy) NSString *date, *astro, *condition, *probability;
@property (nonatomic, copy) NSString *temperature, *humidity, *precipitation;
@property (nonatomic, copy) NSString *pressure, *uv, *visibility, *wind;
@property (nonatomic, readonly) NSString *description;
- (id)initWithJson:(NSDictionary *)nfo;
@end@interface HeWeather : DetailedWeatherInfo
{NSString* mAqi;              // air quality indexHeWeatherInfoNode* mNow;NSArray<HeWeatherInfoNode *> *mForecast;NSArray<HeWeatherInfoNode *> *mDetailedForecast;
}
@end#pragma mark -
#pragma mark put all weather info together to make a report
@interface WeatherFullReport : NSObject
{LocationID mLocation;AirQualityIndex           *mAQI;DetailedWeatherInfo       *mWeatherProvider;
}
@property (nonatomic) LocationID location;
@property (nonatomic, readonly) AirQualityIndex *aqi;
@property (nonatomic, readonly) DetailedWeatherInfo *details;
- (id)initWithLocation:(LocationID)location;
- (void)queryWithCompletionHandler:(void (^)(BOOL aqiReady, BOOL otherReady))handler;
@end

WeatherInfo.mm

//
//  WeatherInfo.mm
//  HuaWeiRemoteCtl
//
//  Created by jia xiaodong on 5/2/16.
//  Modified on 2/7/17
//#import <Foundation/Foundation.h>
#import "WeatherInfo.h"
#include <memory>#pragma mark -
#pragma mark AirQualityIndex
@implementation AirQualityIndex
@synthesize result = mPM2_5;- (id)init
{if (self = [super init]){mPM2_5 = nil;}return self;
}/* After analyzing aqicn.org by Web Developer Tools in Firefox, I got below 2 APIs:https://api.waqi.info/api/feed/@{city_id}/obs.en.json  : super detailed infohttps://api.waqi.info/api/feed/@{city_id}/now.json     : concise infothe 2nd is fairly enough to fit my needs. I only need an AQI value, not its components.*/
- (void)launchQuery:(LocationID)loc completionHander:(void(^)(BOOL success))handler
{NSString* location = [AirQualityIndex StringFromId:loc];NSString* urlTemplate = [[NSString alloc] initWithFormat:@"https://api.waqi.info/api/feed/@%@/now.json", location];NSURL* url = [[NSURL alloc] initWithString:urlTemplate];NSURLSessionDataTask* webRequest = [[NSURLSession sharedSession] dataTaskWithURL:urlcompletionHandler:^(NSData* data, NSURLResponse* response, NSError* error){if (!error && ([(NSHTTPURLResponse*)response statusCode] == 200)){[self parseResponse:data];// callbackif (handler){handler(mPM2_5 != nil);}}}];[webRequest resume];[url release];[urlTemplate release];
}//! the location code is found through Web Developer Tools in Firefox
//! when opening aqicn.org webpage.
+ (NSString*)StringFromId:(LocationID)loc
{switch (loc) {case Beijing_ZhongGuanCun: // Wanliu, Haidianreturn @"452";case Beijing_GuoFengMeiLun:// BDA, Yizhuangreturn @"460";case ShiJiaZhuang_WorkerHospital:return @"644";case QinHuangDao_LuLong:return @"5614";default:return @"";}
}- (void)parseResponse:(NSData*)response
{std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool([[NSAutoreleasePool alloc] init],           // pool ptr[](NSAutoreleasePool* p) { [p release]; }); // deleterNSError* err = nil;NSDictionary* info = [NSJSONSerialization JSONObjectWithData:responseoptions:NSJSONReadingMutableLeaveserror:&err];NSDictionary* dict = [info objectForKey:@"rxs"];if (!dict) {return;}if ([@"1" compare:[dict objectForKey:@"ver"]] != NSOrderedSame) {return;}if ([@"ok" compare:[dict objectForKey:@"status"]] != NSOrderedSame) {return;}dict = [[dict objectForKey:@"obs"] objectAtIndex:0];if ([@"ok" compare:[dict objectForKey:@"status"]] != NSOrderedSame) {return;}dict = [dict objectForKey:@"msg"];NSNumber* aqi = [dict objectForKey:@"aqi"];NSDictionary* city = [dict objectForKey:@"city"];dict = [dict objectForKey:@"time"];NSString* time = [NSString stringWithFormat:@"%@, %@",[dict objectForKey:@"s"], [dict objectForKey:@"tz"]];mPM2_5 = [[NSString alloc] initWithFormat:@"%@\n%@\nAQI (from aqicn.org): %lu\n", time, [city objectForKey:@"name"], [aqi unsignedLongValue]];
}@end#pragma mark -
#pragma mark DetailedWeatherInfo: not a discrete class
@implementation DetailedWeatherInfo
@synthesize options = mWeatherOptions;
@synthesize result;- (id)init
{if (self = [super init]) {mWeatherOptions = WEATHER_NOW;mResult = nil;}return self;
}- (void)dealloc
{[mResult release];[super dealloc];
}@end#pragma mark -
#pragma mark HeWeather: a so called "free forever" service
@implementation HeWeatherInfoNode
@synthesize date, astro, condition, temperature, humidity;
@synthesize precipitation, pressure, uv, visibility, wind;
@synthesize probability;
@dynamic description;- (id)init
{if (self = [super init]) {self.date = nil;self.astro = nil;self.condition = nil;self.temperature = nil;self.humidity = nil;self.probability = nil;self.precipitation = nil;self.pressure = nil;self.uv = nil;self.visibility = nil;self.wind = nil;}return self;
}- (id)initWithJson:(NSDictionary *)nfo
{self = [self init];if (self) {NSString* value = nil;if ((value = [nfo objectForKey:@"date"])) {self.date = [value copy];} else {NSDate* now = [NSDate date];NSDateFormatter *formatter = [[NSDateFormatter alloc] init];[formatter setDateFormat:@"yyyy-MM-dd HH:mm"];self.date = [[formatter stringFromDate:now] copy];[formatter release];}NSDictionary *dict = [nfo objectForKey:@"astro"];if (dict) {NSString *sunrise = [dict objectForKey:@"sr"];NSString *sunset = [dict objectForKey:@"ss"];NSString *moonrise = [dict objectForKey:@"mr"];NSString *moonset = [dict objectForKey:@"ms"];self.astro = [[NSString alloc] initWithFormat:@"日升落: %@ - %@; 月升落: %@ - %@", sunrise, sunset, moonrise, moonset];}if ((dict = [nfo objectForKey:@"cond"])) {if ((value = [dict objectForKey:@"txt"])) {self.condition = [value copy];} else {self.condition = [[NSString alloc] initWithFormat:@"日%@,夜%@", [dict objectForKey:@"txt_d"], [dict objectForKey:@"txt_n"]];}}if ((value = [nfo objectForKey:@"tmp"])) {if ([value isKindOfClass:[NSDictionary class]]) {dict = [nfo objectForKey:@"tmp"];value = [NSString stringWithFormat:@"[%@,%@]", [dict objectForKey:@"min"], [dict objectForKey:@"max"]];}NSMutableString* temp = [NSMutableString stringWithFormat:@"温度: %@", value];if ((value = [nfo objectForKey:@"fl"])) {[temp appendFormat:@"; 体感温度: %@", value];}self.temperature = [temp copy];}if ((value = [nfo objectForKey:@"hum"])) {self.humidity = [[NSString alloc] initWithFormat:@"相对湿度: %@%%", value];}if ((value = [nfo objectForKey:@"pop"])) {float p = [value floatValue];if (p > 0) {self.probability = [[NSString alloc] initWithFormat:@"降水概率: %d%%", NSUInteger(p*100)];}}if ((value = [nfo objectForKey:@"pcpn"])) {if ([value floatValue] > 0) {self.precipitation = [[NSString alloc] initWithFormat:@"降水量: %@mm", value];}}if ((value = [nfo objectForKey:@"pres"])) {self.pressure = [[NSString alloc] initWithFormat:@"大气压: %@mmHg", value];}if ((value = [nfo objectForKey:@"uv"])) {self.uv = [[NSString alloc] initWithFormat:@"紫外线: %@", value];}if ((value = [nfo objectForKey:@"vis"])) {self.visibility = [[NSString alloc] initWithFormat:@"能见度: %@km", value];}if ((dict = [nfo objectForKey:@"wind"])) {NSString *dir = [dict objectForKey:@"dir"];NSString *lvl = [dict objectForKey:@"sc"];NSString *spd = [dict objectForKey:@"spd"];self.wind = [[NSString alloc] initWithFormat:@"%@%@级, %@km/h", dir, lvl, spd];}}return self;
}- (void)dealloc
{[date release];[astro release];[condition release];[temperature release];[humidity release];[probability release];[precipitation release];[pressure release];[uv release];[visibility release];[wind release];[super dealloc];
}- (NSString *)description
{NSMutableString *desc = [NSMutableString stringWithFormat:@"------- %@ -------", date];if (condition) {[desc appendFormat:@"\n%@", condition];}if (astro) {[desc appendFormat:@"\n%@", astro];}if (temperature) {[desc appendFormat:@"\n%@", temperature];}if (humidity) {[desc appendFormat:@"\n%@", humidity];}if (probability) {[desc appendFormat:@"\n%@", probability];}if (precipitation) {[desc appendFormat:@"\n%@", precipitation];}if (pressure) {[desc appendFormat:@"\n%@", pressure];}if (uv) {[desc appendFormat:@"\n%@", uv];}if (visibility) {[desc appendFormat:@"\n%@", visibility];}if (wind) {[desc appendFormat:@"\n%@", wind];}return [desc copy];
}@end@implementation HeWeather
@dynamic result;- (id)init
{if (self = [super init]) {mAqi = nil;mNow = nil;mForecast = nil;mDetailedForecast = nil;}return self;
}- (void)dealloc
{[mAqi release];[mNow release];//[mForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {//    [obj release];//}];[mForecast release];//[mDetailedForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {//    [obj release];//}];[mDetailedForecast release];[super dealloc];
}+ (NSString*)StringFromId:(LocationID)loc
{switch (loc) {case Beijing_GuoFengMeiLun: // Tongzhou, Beijingreturn @"CN101010600";case Beijing_ZhongGuanCun:return @"CN101010200";  // Haidian, Beijingcase ShiJiaZhuang_WorkerHospital:return @"CN101090101";  // Shijiazhuang Citycase QinHuangDao_LuLong:return @"CN101091105";  // Lulong County, Qinhuangdao Citydefault:return @"CN101010100"; // beijing}
}- (void)launchQuery:(LocationID)loc completionHander:(void(^)(BOOL success))handler
{NSString* serviceTemplate = @"https://free-api.heweather.com/v5/[api]?key=2dae4ca04d074a1abde0c113c3292ae1&city=";serviceTemplate = [serviceTemplate stringByReplacingOccurrencesOfString:@"[api]" withString:@"weather"];serviceTemplate = [serviceTemplate stringByAppendingString:[HeWeather StringFromId:loc]];NSURL* url = [NSURL URLWithString:serviceTemplate];NSURLSessionDataTask* webRequest = [[NSURLSession sharedSession] dataTaskWithURL:urlcompletionHandler:^(NSData* data, NSURLResponse* response, NSError* error){BOOL success = (!error && ([(NSHTTPURLResponse*)response statusCode] == 200));if (success){[self parseResponse:data];}if (handler) // callback{handler(success);}}];[webRequest resume];
}- (void)parseResponse:(NSData*)response
{std::unique_ptr<NSAutoreleasePool, void(*)(NSAutoreleasePool*)> scopePool([[NSAutoreleasePool alloc] init],           // pool ptr[](NSAutoreleasePool* p) { [p release]; }); // deleterNSError* err = nil;NSDictionary* info = [NSJSONSerialization JSONObjectWithData:responseoptions:NSJSONReadingMutableLeaveserror:&err];NSArray* array = [info objectForKey:@"HeWeather5"];if (!array || [array count] == 0) {return;}NSDictionary* node = [array objectAtIndex:0]; // only 1 node available and meaningfulif ([@"ok" compare:[node objectForKey:@"status"]] != NSOrderedSame) {return;}[self parseAqi:[[node objectForKey:@"aqi"] objectForKey:@"city"]];[self parseNowInfo:[node objectForKey:@"now"]];if (self.options & WEATHER_FORECAST){[self parseDailyForecast:[node objectForKey:@"daily_forecast"]];}if (self.options & WEATHER_DETAIL_FORECAST){[self parseDetailedForecast:[node objectForKey:@"hourly_forecast"]];}
}- (void)parseAqi:(NSDictionary *)info
{NSString* aqi = [info objectForKey:@"aqi"];NSString* p10 = [info objectForKey:@"pm10"];NSString* p25 = [info objectForKey:@"pm25"];NSString* qty = [info objectForKey:@"qlty"];mAqi = [[NSString alloc] initWithFormat:@"AQI:%@, PM10:%@, PM2.5:%@, %@", aqi, p10, p25, qty];
}- (void)parseNowInfo:(NSDictionary *)now
{mNow = [[HeWeatherInfoNode alloc] initWithJson:now];
}- (void)parseDailyForecast:(NSArray *)forecast
{NSMutableArray<HeWeatherInfoNode *> *array = [[NSMutableArray alloc] init];[forecast enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {HeWeatherInfoNode *node = [[HeWeatherInfoNode alloc] initWithJson:obj];[array insertObject:node atIndex:idx];[node release];}];mForecast = [[NSArray alloc] initWithArray:array];[array release];
}- (void)parseDetailedForecast:(NSArray *)forecast
{NSMutableArray<HeWeatherInfoNode *> *array = [[NSMutableArray alloc] init];[forecast enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) {HeWeatherInfoNode *node = [[HeWeatherInfoNode alloc] initWithJson:obj];[array insertObject:node atIndex:idx];[node release];}];mDetailedForecast = [[NSArray alloc] initWithArray:array];[array release];
}- (NSString *)result
{if (mNow) {NSMutableString* tmp = [[NSMutableString alloc] initWithString:mNow.description];if (self->mAqi) {[tmp appendFormat:@"\n%@\n", self->mAqi];}[mForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {[tmp appendFormat:@"\n%@\n", obj.description];}];[mDetailedForecast enumerateObjectsUsingBlock:^(HeWeatherInfoNode *obj, NSUInteger idx, BOOL *stop) {[tmp appendFormat:@"\n%@", obj.description];}];mResult = [tmp copy];[tmp release];}return mResult;
}@end#pragma mark -
#pragma mark WeatherFullReport
@implementation WeatherFullReport
@synthesize aqi = mAQI;
@synthesize details = mWeatherProvider;
@synthesize location = mLocation;- (id)initWithLocation:(LocationID)loc
{if (self = [super init]) {mLocation = loc;mAQI = [[AirQualityIndex alloc] init];mWeatherProvider = [[HeWeather alloc] init];}return self;
}- (void) queryWithCompletionHandler:(void (^)(BOOL aqiReady, BOOL otherReady))handler;
{[mAQI launchQuery:mLocation completionHander:^(BOOL success){if (handler){handler(YES, NO);}}];[mWeatherProvider launchQuery:mLocation completionHander:^(BOOL success){if (handler){handler(NO, YES);}}];
}- (void) dealloc
{[mAQI release];[mWeatherProvider release];[super dealloc];
}@end

iOS app: Weather Forecast and Huawei Remote Control相关推荐

  1. 2011斯坦福大学iOS应用开发教程学习笔记(第二课)My First iOS App

    2019独角兽企业重金招聘Python工程师标准>>> 第二课名称是: My First iOS App 我的第一个iOS应用 注意:我用的是XCode Version 4.5.2 ...

  2. python脚本控制ios手机app_appium 下 python 脚本自动化测试iOS APP 实例

    环境:Mac,Xcode, appium python 本文基于appium 环境搭建成功后.如何使用python 编写脚本测试iOS APP 1.下载python-client https://gi ...

  3. 收集各种 iOS App 开发可以用到的代码示例

    code4app.com 这网站不错,收集各种 iOS App 开发可以用到的代码示例  cocoacontrols.com/ 英文版本的lib收集  objclibs.com/ 精品lib的收集网站 ...

  4. ios服务器管理系统,ios app云服务器

    ios app云服务器 内容精选 换一换 本节操作介绍如何在移动设备上连接Linux实例.以iTerminal-SSH Telnet为例介绍如何在iOS设备上连接 Linux 实例,详细操作请参考IO ...

  5. 【转载】Allowing applications to play nice(r) with each other: Handling remote control buttons

    下文转自http://android-developers.blogspot.com/2010/06/allowing-applications-to-play-nicer.html 因为原文不知道什 ...

  6. iOS APP安全杂谈之二

    高小厨 · 2015/08/12 10:06 0x00 序 自从上一篇文章发了以后,参加乌云众测时发现小伙伴们真的提交了一些我所说的IOS低风险问题,真的是连一百块都不留给我.但是我依然感到幸运,因为 ...

  7. iOS APP 逆向安全杂谈之二

    一些公用类: @interface ClassCustomClass :NSObject{ NSString *varTest1; NSString *varTest2; NSString *varT ...

  8. Xcode couldn‘t find any iOS App Development provisioning profiles matching ‘com.example.***‘

    在更新完iOS14.3后,Xcode真机调试时报错,无法进行真机测试: 报以下错误: No profiles for 'com.example.software.Login' were found: ...

  9. ios app 砸壳

    这里介绍使用dumpdecrypted砸壳.原理是用DYLD_INSERT_LIBRARIES这个环境变量加载脱壳的动态链接库dumpdecrypted.dylib 1.ssh连接上越狱的机器,输入密 ...

最新文章

  1. ROS中使用摄像头的问题
  2. angularjs1访问子组件_vue 组件通信看这篇就够了(12种通信方式)
  3. 问候Maven3(笔记一)
  4. 简单API接口签名验证
  5. Python-OpenCV 笔记8 -- PIL.Image和OpenCV图像格式转换
  6. Smart template的控件能否当成普通控件来用 1
  7. systemverilog数据类型
  8. C#中的is、as及转换
  9. 浏览器缓存机制的研究分享
  10. 【2019icpc南京站网络赛 - F】Greedy Sequence(思维,贪心构造,STLset)
  11. 讲解Linux数据库安装
  12. struts2解决动态多文件上传的问题(上传文件与数据库字段一一对应)(转)
  13. 曾经拒绝马云的实习生 他说要开启云工作时代
  14. PAT1036.跟奥巴马一起编程
  15. c语言如何调用外部文件的函数调用,keil 中如何调用其他文件的函数
  16. PE教程6: Import Table(引入表)(看雪)
  17. atoi,itoa,strcpy, strcmp,strcpy, strcpy_s, memc...
  18. 【Android-混合开发】mPaas-多版本接入篇
  19. 天地超云高温一体机的耐热秘诀
  20. OpenCV学习笔记 - OpenCV必知必会的基础

热门文章

  1. Windchill中的基本业务对象
  2. 500g硬盘装linux怎样分,500G的硬盘,怎么分区比较合理?
  3. 计算机进制编码怎么算,二进制编码-详细讲解
  4. tf.app.flags.DEFINE_string()和tf.app.flags.FLAGS和tf.app.run()
  5. java入门提高篇:Day1 抽象类
  6. Windows下Qt拔插U盘的检测方法
  7. 《数据可视化》之小白学习篇(一)
  8. LVN与其在Linux上的实现
  9. 你的路由器可能并不安全:中情局其实已监控多年
  10. 赚钱不易,副业成刚需,分享3个适合普通人做自媒体短视频的方向