当前位置: 首页 > 科技观察

Widget

时间:2023-03-19 00:32:52 科技观察

带着枷锁在针尖上翩翩起舞自从苹果在iOS10中对widget进行了大改版之后,很多人都开发了自己的widget。网上也有很多教程告诉你如何快速开发一个widget。我的这篇文章不会重复简单的创建扩展添加证书之类的。我们必须深入了解小部件应该是什么样子。你真的了解widget的尺寸吗?首先widget有两种状态:typedefNS_ENUM(NSInteger,NCWidgetDisplayMode){NCWidgetDisplayModeCompact,//FixedheightNCWidgetDisplayModeExpanded,//Variableheight}网上的教程大部分都会告诉你,如果要改变widget的高度,就是Write这在以下方法中-(void)widgetActiveDisplayModeDidChange:(NCWidgetDisplayMode)activeDisplayModewithMaximumSize:(CGSize)maxSize{if(activeDisplayMode==NCWidgetDisplayModeCompact){self.preferredContentSize=CGSizeMake(maxSize.width,110);}else{self.preferredCont=CGSizeMake(maxSize.width,300);}}意思是折叠状态是110,展开状态是300。因为如果你在折叠状态写120,那么高度还是110,这个高度是不会变的。在展开状态下,当然应该取小于maxSize.height的值。那么maxSize的值是多少呢?但是,我要告诉你,身高根本就不是一个固定值!而且可以认为是不规则的!!!因为整个widget的maxSize限制规则是根据系统字体大小变化的。处于折叠或展开状态。也就是说,直接写110是错误的。因为在默认系统字体下,折叠高度确实是110。但是一旦系统字体改为最小,widget折叠状态的高度只有95,而在最大系统字体的情况下,高度widget折叠状态的大小为140。系统字体大小共有7个文件。也就是说,折叠高度和字体大小是相关的,但不是线性相关的。可以验证折叠的高度为95-100-105-110-120-135-140。并且不能修改。轻折叠的高度很好。展开高度也是一个非线性相关的高度(并且在统一折叠高度的情况下)。下面讨论的扩展高度,都是将系统字体大小固定为默认大小,并控制变量(最终的大小结果,理论上乘以7,就是所有可能的高度)。首先是车型的不同。当然,手机屏幕越小,展开高度越小,其实也是可以接受的。大不了我们是根据最小屏的情况来开发的。但是,我要告诉你,widget的高度还是会变化的!这是我们最常见的小部件入口,也就是在屏幕上向左滑动的今日页面。不过其实还有一个入口,就是下拉通知页面的左滑,里面也会有两个入口,widget在展开状态下的最大高度,后者会比前者小很多!可以通过查看中断点处的maxSize轻松验证,即展开状态下iPhone7的默认字体大小。第一个入口的maxSize.height是616,另外一个入口,这个值就变成了528,这时候真想问问苹果爸爸,这是想干什么?其实还有第三个入口,就是3Dtouchappicon,widgets也会出现,但是只有折叠状态。也就是说目前折叠状态下有7个尺寸,展开状态下每个屏幕尺寸为7*2,也就是说如果4英寸、4.7英寸、5.5英寸这三种主流屏幕尺寸一定要适配,展开状态是7*2*3=42尺寸。看到这里,你可以说,没关系,我就拿4寸机的最小高度吧。这个要看你的设计师能不能同意。你觉得结束了吗?不要谈论iPad,我们不考虑它。iPhone能放下,iPad当然也能放下。但是你真的想不到,5.5英寸是Plus机型的横屏状态,也是不一样的尺寸。Plus横屏展开模式下,第一个入口最大高度只有352,第二个入口最大高度只有264……什么意思,字体情况下的折叠状态有高度为140,展开后不到其折叠高度的两倍。如果您对widget尺寸适配感兴趣并有解决方案,请联系我,非常感谢。你觉得眼花缭乱吗?如果你添加了很多widgets,你会发现仅仅在列表中上下滑动就能让你眼花缭乱。Widget本身的更新机制是进入Widget后先执行viewDidLoad方法,再执行viewWillAppear方法。但是经过测试,每当某个Widget上下滑动,滑出屏幕,再将widget滑回来,就会用到上面的刷新机制。由于以上特点,更新代码最好写在viewWillAppear方法中。对于那些更新时效性特别强的,比如天气app,最好的做法是在方法中添加一个NSTimer定时刷新,在viewWillDisAppear方法中进行。取消NSTimer的定时更新就失效了。或者,您可以自己实现缓存,这也可以进行优化。判断如果请求的数据与当前数据内容一致,则不会刷新列表操作。知乎、Get等很多APP的Widget只要使用viewDidLoad方法就会闪一次,因为Widget每次加载请求的数据都会被替换一次。至于为什么只要看不见再回来就刷新,估计是内存的问题吧。小部件对内存的要求高得离谱。一旦你的widget里面有gif,基本上是根本没办法显示的,过一会就显示加载不出来。不仅如此,反复来回滚动小部件页面以保持刷新也会增加内存占用。我不知道这是不是Apple的bug,但在我自己的测试中,我尽量保持单个widget的内存占用小于15M。杀死内存的可能性很小。所以,我在开发的时候,gif图片只拍了***帧。并且尽量不要主动刷新UI,让widget内存保持在低水平。而且由于扩展实际上不能直接在主要目标中使用那些框架,所以我也写了一些基本的功能组件。首先当然是缓存系统了。图片缓存尤为关键,因为小部件的特性会被反复刷新。如果没有缓存系统,那将是一种巨大的浪费。首先就是图片保存:#import"QDTEImageCache.h"#import@implementationQDTEImageCache+(instancetype)shareImageCache{staticdispatch_once_tonce;staticidinstance;dispatch_once(&once,^{instance=[selfnew];});returninstance;}-(BOOL)isExistCacheForKey:(NSString*)key{key=[selfcachedFileNameForKey:key];NSString*filePath=[[selfgetCachePath]stringByAppendingPathComponent:key];返回[[NSFileManagerdefaultManager]fileExistsAtPath:filePath];}-(NSData*)getImageDataForKey:(NSString*)key{if([selfisExistCacheForKey:key]){return[NSDatadataWithContentsOfFile:[[selfgetCachePath]stringByAppendingPathComponent:[selfcachedFileNameForKey:key]]];}returnnil;}-(void)saveToCacheWithData:(NSData*)dataforKey:(NSString*)key{key=[selfcachedFileNameForKey:key];NSString*filePath=[[selfgetCachePath]stringByAppendingPathComponent:key];dispatch_async(dispatch_get_global_queue(0,0),^{[datawriteToFile:filePathatomically:YES];});}-(NSString*)getCachePath{NSFileManager*fileMgr=[NSFileManagerdefaultManager];NSString*containerPath=[[fileMgrcontainerURLForSecurityApplicationGroupIdentifier:@"group.com.XXXXXXX"]path];NSString*path=[containerPathstringByAppendingString:@"/Caches/"];if(![fileMgrfileExistsAtPath:path]){BOOLres=[fileMgrcreateDirectoryAtPath:pathwithIntermediateDirectories:YESattributes:nilerror:nil];if(!res){returnnil;}}returnpath;}-(NSString*)cachedFileNameForKey:(NSString*)key{constchar*str=[keyUTF8String];if(str==NULL){str="";}unsignedcharr[CC_MD5_DIGEST_LENGTH];CC_MD5(str,(CC_LONG)strlen(str),r);NSString*filename=[NSStringstringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%@",r[0],r[1],r[2],r[3],r[4],r[5],r[6],r[7],r[8],r[9],r[10],r[11],r[12],r[13]],r[14],r[15],[[keypathExtension]isEqualToString:@""]?@"":[NSStringstringWithFormat:@".%@",[keypathExtension]]];returnfilename;}@end一个非常基础同时配合文件管理类对接口返回的response进行管理:controller发送的request,当收到response的数据时,做一个缓存并进行比较-(void)URLSession:(NSURLSession*)sessiondataTask:(NSURLSessionDataTask*)dataTaskdidReceiveData:(NSData*)data{[self.jsonDataappendData:data];NSDictionary*dic=[[NSJSONSerializationJSONObjectWithData:self.jsonDataoptions:NSJSONReadingMutableContainerserror:nil]copy];if(dic==nil)return;self.jsonData=nil;NSDictionary*metaDic=[dicvalueForKey:@"meta"];if([[metaDicvalueForKey:@"status"]integerValue]==200){NSArray*papers=[[dicvalueForKey:@"response"]valueForKey:@"papers"];NSDictionary*paperDic=[papersfirstObject];[_fileMgrsaveToCacheWithRawDic:paperDic];QDTELabModel*labModel=[selfmodelFromRawDic:paperDic];if(labModel.article_id.longValue==self.labModel.article_id.longValue)返回;self.labModel=labModle;dispatch_async(dispatch_get_main_queue(),^{for(UIView*subViewinself.view.subviews){[subViewremoveFromSuperview];}[selfrefreshContentView];});}}文件管理类使用存储#import"QDTEFileManager.h"@implementationQDTEFileManager+(instancetype)shareManager{staticdispatch_once_tonce;staticidinstance;dispatch_once(&once,^{instance=[selfnew];});returninstance;}-(NSDictionary*)getUserinfo{NSFileManager*fileMgr=[NSFileManagerdefaultManager];NSString*containerPath=[[fileMgrcontainerURLForSecurityApplicationGroupIdentifier:@"group.com.XXXXXX"]path];NSString*filePath=[containerPathstringByAppendingPathComponent:@"QDUserinfo.json"];if([fileMgrfileExistsAtPath:filePath]){NSError*error;返回[[NSJSONSerializationJSONObjectWithData:[NSDatadataWithContentsOfFile:filePath]options:NSJSONReadingMutableContainerserror:&error]copy];}returnnil;}-(NSDictionary*)getRawDicFromCache{NSFileManager*fileMgr=[NSFileManagerdefaultManager];NSString*containerPath=[[fileMgrcontainerURLForSecurity:@ApplicationGroupId.com.XXXXXX"]path];NSString*path=[containerPathstringByAppendingString:@"/Caches/"];NSString*filePath=[pathstringByAppendingPathComponent:@"QDLabCache.json"];if([fileMgrfileExistsAtPath:filePath]){NSError*error;NSDictionary*rawDic=[[NSJSONSerializationJSONObjectWithData:[NSDatadataWithContentsOfFile:filePath]options:NSJSONReadingMutableContainerserror:&error]copy];returnrawDic;}returnil;}-(void)saveToCacheWithRawDic:(NSDictionary*)rawDic{NSFileManager*fileMgr=[NSFileManagerdefaultManager];NSString*containerPath=[[fileMgrcontainerURLForSecurityApplicationGroupIdentifier:@”group.com。XXXXXX"]path];NSString*path=[containerPathstringByAppendingString:@"/Caches/"];BOOLres=[fileMgrcreateDirectoryAtPath:pathwithIntermediateDirectories:YESattributes:nilerror:nil];if(!res){return;}NSString*filePath=[pathstringByAppendingPathComponent:@"QDLabCache.json"];if([NSJSONSerializationisValidJSONObject:rawDic]){NSError*error;NSData*jsonData=[NSJSONSerializationdataWithJSONObject:rawDicoptions:NSJSONWritingPrettyPrintederror:&error];dispatch_async(dispatch_get_global_queue(0,0),^{[jsonDatawriteToFile:filePathatomically:YES];});}}-(NSString*)getServerIP{if([selfgetDEBUG]){NSFileManager*fileMgr=[NSFileManagerdefaultManager];NSString*containerPath=[[fileMgrcontainerURLForSecurityApplicationGroupIdentifier:@"group.com.XXXXXX"]path];NSString*filePath=[containerPathstringByAppendingPathComponent:@"QDServerIP.json"];if([fileMgrfileExistsAtPath:filePath]){N错误;NSArray*serverIPArr=[[NSJSONSerializationJSONObjectWithData:[NSDatadataWithContentsOfFile:filePath]options:NSJSONReadingMutableContainerserror:&error]copy];returnserverIPArr.firstObject;}}return@"http://app3.qdaily.com";}-(BOOL)getDEBUG{#ifdefDEBUGreturnYES;#elifBETAreturnYES;#elsereturnNO;#endif}@end***,这是我的一个小部件的文件结构。虽然widget很小,但是我在开发的时候还是想过它有多复杂。毕竟,这栽什么东西,开发一次,几乎就再也不会动了。毕竟……带着枷锁在针尖上跳舞太累了……。