分类: IOS技术

IOS技术

ios自定义控件复选框和单选框的实现

我们先实现单个按钮,为了复用,不管单选还是复选按钮都是使用同一个类来实现,为了区别单选还是复选,我们用一个自定义枚举类型CheckButtonStyle属性style来区别,当其值设置为CheckButtonStyleDefault或CheckButtonStyleBox时,为复选按钮:

  当其值设为CheckButtonStyleRadio时,为单选按钮:

当按钮在选中/反选状态间切换时,文字左边的图片自动转换。

整个控件是由一个ImageView、一个Label、一个BOOL变量及其他变量组成,.h文件如下:

typedef enum {

    CheckButtonStyleDefault 0 ,

    CheckButtonStyleBox 1 ,

    CheckButtonStyleRadio 2

} CheckButtonStyle;

#import <Foundation/Foundation.h>

 

@interface CheckButton : UIControl {

//UIControl* control;

UILabel label ;

UIImageView icon ;

BOOL checked ;

id value , delegate ;

CheckButtonStyle style ;

NSString * checkname ,* uncheckname ; // 勾选/反选时的图片文件名

}

@property ( retain , nonatomic ) id value,delegate;

@property ( retain , nonatomic )UILabel* label;

@property ( retain , nonatomic )UIImageView* icon;

@property ( assign )CheckButtonStyle style;

-( CheckButtonStyle )style;

-( void )setStyle:( CheckButtonStyle )st;

-( BOOL )isChecked;

-( void )setChecked:( BOOL )b;

@end

具体实现如下:

#import “CheckButton.h”

 

 

@implementation CheckButton

@synthesize label,icon,value,delegate;

-( id )initWithFrame:( CGRect ) frame

{

if ( self =[ super initWithFrame : frame ]) {

icon =[[ UIImageView alloc ] initWithFrame :

  CGRectMake ( 10 0 frame . size . height frame . size . height )];

[ self setStyle : CheckButtonStyleDefault ]; // 默认风格为方框(多选)样式

//self.backgroundColor=[UIColor grayColor];

[ self addSubview : icon ];

label =[[ UILabel alloc ] initWithFrame : CGRectMake ( icon . frame . size . width + 24 0 ,

   frame . size . width  icon . frame . size . width  24 ,

   frame . size . height )];

label . backgroundColor =[ UIColor clearColor ];

label . font =[ UIFont fontWithName : @”Arial” size : 20 ];

label . textColor =[ UIColor

  colorWithRed : 0xf9 / 255.0

  green : 0xd8 / 255.0

  blue : 0x67 / 255.0

  alpha : 1 ];

label . textAlignment = UITextAlignmentLeft ;

[ self addSubview : label ];

[ self addTarget : self action : @selector ( clicked forControlEvents : UIControlEventTouchUpInside ];

}

return self ;

}

-( CheckButtonStyle )style{

return style ;

}

-( void )setStyle:( CheckButtonStyle )st{

style =st;

switch ( style ) {

case CheckButtonStyleDefault :

case CheckButtonStyleBox :

checkname = @”checked.png” ;

uncheckname = @”unchecked.png” ;

break ;

case CheckButtonStyleRadio :

checkname = @”radio.png” ;

uncheckname = @”unradio.png” ;

break ;

default :

break ;

}

[ self setChecked : checked ];

}

-( BOOL )isChecked{

return checked ;

}

-( void )setChecked:( BOOL )b{

if (b!= checked ){

checked =b;

}

if ( checked ) {

[ icon setImage :[ UIImage imageNamed : checkname ]];

} else {

[ icon setImage :[ UIImage imageNamed : uncheckname ]];

}

}

-( void )clicked{

[ self setChecked :! checked ];

if ( delegate != nil ) {

SEL sel= NSSelectorFromString ( @”checkButtonClicked” );

if ([ delegate respondsToSelector :sel]){

[ delegate performSelector :sel];

}

}

-( void )dealloc{

value = nil ; delegate = nil ;

[ label release ];

[ icon release ];

[ super dealloc ];

}

@end

使用CheckButton类很简单,构造、设置标签文本等属性,然后addSubview:

CheckButton * cb=[[ CheckButton a lloc initWithFrame : CGRectMake ( 20 60 260 32 )];

  1. label. text = @”checkbutton1″ ;
  2. value=[[ NSNumber alloc ] initWithInt : 18 ];
  3. style= CheckButtonStyleDefault ;

[ self . view addSubview :cb];

二、单选按钮组的实现

复选按钮无所谓“组”的概念,单选按钮则不同。在同一个组中,单选按钮只允许同时选择一个按钮,不能选多个,因此我们要实现一个单选按钮组的类:

#import <Foundation/Foundation.h>

#import “CheckButton.h”

 

@interface RadioGroup : NSObject {

NSMutableArray children ;

NSString text ;

id value ;

}

@property ( readonly )NSString* text;

@property ( readonly ) id value;

-( void )add:( CheckButton *)cb;

-( void )checkButtonClicked:( id )sender;

@end

#import “RadioGroup.h”

 

 

@implementation RadioGroup

@synthesize text,value;

-( id )init{

if ( self =[ super init ]){

children =[[ NSMutableArray alloc ] init ];

}

return self ;

}

-( void )add:( CheckButton *)cb{

  1. delegate= self ;

if (cb. checked ) {

text =cb. label . text ;

value =cb. value ;

}

[ children addObject :cb];

}

-( void )checkButtonClicked:( id )sender{

CheckButton * cb=( CheckButton *)sender;

if (!cb. checked ) {

// 实现单选

for ( CheckButton * each in children ){

if (each. checked ) {

[each setChecked : NO ];

}

}

[cb setChecked : YES ];

// 复制选择的项

text =cb. label . text ;

value =cb. value ;

}

NSLog ( @”text:%@,value:%d” , text ,[( NSNumber *) value intValue ]);

}

-( void )dealloc{

[ text release ];

value = nil ;

[ children release ];

[ super dealloc ];

}

@end

单选按钮组在ViewController中的使用:

-( id )initWithNibName:( NSString *)nibNameOrNil bundle:( NSBundle *)nibBundleOrNil{

if ( self =[ super initWithNibName :nibNameOrNil bundle :nibBundleOrNil]){

// 单选按钮组

rg =[[ RadioGroup alloc ] init ];

// 第 1 个单选按钮

CheckButton * cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 60 260 32 )];

// 把单选按钮加入按钮组

[ rg add :cb];

  1. label. text = @”★” ;
  2. value=[[ NSNumber alloc ] initWithInt : 1 ];

// 把按钮设置为单选按钮样式

  1. style= CheckButtonStyleRadio ;

// 加入视图

[ self . view addSubview :cb];

[cb release ]; //add 后,会自动持有,可以释放

// 第 2 个单选按钮

cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 100 260 32 )];

[ rg add :cb];

  1. label. text = @”★★” ;
  2. value=[[ NSNumber alloc ] initWithInt : 2 ];
  3. style= CheckButtonStyleRadio ;

[ self . view addSubview :cb];

[cb release ];

// 第 3 个单选按钮

cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 140 260 32 )];

// 各种属性必须在 [rg addv] 之前设置,否则 text 和 value 不会被 populate

  1. checked= YES ;
  2. label. text = @”★★★” ;
  3. value=[[ NSNumber alloc ] initWithInt : 3 ];
  4. style= CheckButtonStyleRadio ;

[ self . view addSubview :cb];

[ rg add :cb]; // 属性设置完之后再 add

[cb release ];

// 第 4 个单选按钮

cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 180 260 32 )];

[ rg add :cb];

  1. label. text = @”★★★★” ;
  2. value=[[ NSNumber alloc ] initWithInt : 4 ];
  3. style= CheckButtonStyleRadio ;

[ self . view addSubview :cb];

[cb release ];

// 第 5 个单选按钮

cb=[[ CheckButton alloc ] initWithFrame : CGRectMake ( 20 220 260 32 )];

[ rg add :cb];

  1. label. text = @”★★★★★” ;
  2. value=[[ NSNumber alloc ] initWithInt : 5 ];
  3. style= CheckButtonStyleRadio ;

[ self . view addSubview :cb];

[cb release ];

}

return self ;

}

运行效果:

%title插图%num

iOS开发经常用到的技术知识点

1.刷新单个tableviewcell

NSIndexPath * indexPat=[NSIndexPath indexPathForRow:indexPlay inSection:0];

NSArray * indexArray=[NSArray arrayWithObject:indexPat];

[self.tableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];

2.   判断该方法是否执行??

BOOL isss=  [cell.queuePlayer respondsToSelector:@selector(play)];

instancesRespondToSelector是指类的实例们是否能响应某一个方法(类操作),respondsToSelector是指类是否能响应某一方法(对象)
3.代码块的使用
int (^oneFrom)(int) = ^(int anInt) {

return anInt -1;

};

NSLog(@”%d”,oneFrom(10));

4.  改变buuton的高亮

UIImageView * iv = [[UIImageView alloc] initWithFrame:CGRectMake(250, 5, 50, 34)];

iv.userInteractionEnabled = YES;

UIButton * navBtn = [UIButton buttonWithType:UIButtonTypeCustom];

navBtn.frame = CGRectMake(0, 0, 50, 34);

[navBtn setImage:[UIImage imageNamed:@”rong_Tian”] forState:UIControlStateNormal];

[navBtn setHighlighted:YES];

[navBtn addTarget:self action:@selector(btnPressed:) forControlEvents:UIControlEventTouchUpInside];

[navBtn setShowsTouchWhenHighlighted:YES];

[iv addSubview:navBtn];

[self.navigationController.navigationBar addSubview:iv];

5. 默认为cell*行

 

NSIndexPath *first=[NSIndexPath indexPathForRow:0 inSection:0];

[self.tableView selectRowAtIndexPath:first animated:YES scrollPosition:UITableViewScrollPositionBottom];

6.一个项目中  ARC和非ARC 的混合使用

 

点击项目–》TARGETS-》Build Phases  -》Compile  Sources   中选择要改的.m   双击   在标签中写:

1.如果是ARC项目,要加入非ARC的代码文件  fobjc-arc

2.如果是非ARC,要加入ARC的代码   -fno-objc-arc       Enter就OK

 

//    NSURL * url=[NSURL URLWithString:str];

//

//      NSURLRequest *requestt = [[NSURLRequest alloc]initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];

//     NSData *received = [NSURLConnection sendSynchronousRequest:requestt returningResponse:nil error:nil];

//    NSString *strr = [[NSString alloc]initWithData:received encoding:NSUTF8StringEncoding];

//    NSLog(@”这是  成功返回的信息     %@”,strr);

7.距离感应器

UIDeviceOrientation orientation3=  [[UIDevice currentDevice] orientation];

 

NSLog(@”获取当前状态   %d”,orientation3);

[[UIDevice currentDevice] setProximityMonitoringEnabled:YES];

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(sensorStateChange:)

name:@”UIDeviceProximityStateDidChangeNotification”

 

object:nil];

-(void)sensorStateChange:(NSNotificationCenter *)notification;

{

if ([[UIDevice currentDevice] proximityState] == YES) {

 

NSLog(@”Device is close to user”);

//在此写接近时,要做的操作逻辑代码

}else{

NSLog(@”Device is not close to user”);

}

}

8.获得cookie

 

NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];

for (NSHTTPCookie *cookie in [cookieJar cookies]) {

NSLog(@”cookie=====     %@”, cookie);

}

9.从相册中只获得视频文件

 

if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] == YES)

{

UIImagePickerController *videoLibraryController = [[[UIImagePickerController alloc] init] autorelease];

videoLibraryController.delegate = self;

videoLibraryController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;

videoLibraryController.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeMovie];

[videoLibraryController setAllowsEditing:YES];

[self.navigationController presentViewController:videoLibraryController animated:YES completion:^{

 

}];

}

else

{

[self AlertlogError:@”暂时你还没有视频”];

}

10. 读取全局的Delegate:
KiloNetAppDelegate *appdelegate = (KiloNetAppDelegate *)[[UIApplication sharedApplication] delegate];

11.键盘透明

textField.keyboardAppearance = UIKeyboardAppearanceAlert;
12.URL错误:
Error Domain=ASIHTTPRequestErrorDomain Code=5 “Unable to create request (bad url?)” UserInfo=0x69ba0f0 {NSLocalizedDescription=Unable to create request (bad url?)}

解决办法:

NSString*url =@”http://oerp.xixingsoft.com:8083/oadata/MobileConfig.nsf/GetStandList?openagent&ViewNumber=新闻中心”;

url=[url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

 

NSStringEncodingenc = CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingGB_18030_2000);//gbk code–>utf8 code

NSData*data = [url dataUsingEncoding:NSUTF8StringEncoding];//[request responseData];

NSString*utf8str = [[[NSStringalloc] initWithData:data encoding:enc] autorelease];

13.请求中加cookie、 heard

当你需要添加更多的请求信息时,如,添加个请求Header:
[request addRequestHeader:@”name” value:@”Jory lee”];

14 Plist文件的保存 修改    除非在decument是可读可写的(在工程中  可读不可写)

//获取路径对象

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

//获取完整路径

NSString *documentsDirectory = [paths objectAtIndex:0];

NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:@”test.plist”];

NSLog(@”plist  地质   %@”,plistPath);

NSMutableDictionary *dictplist = [[NSMutableDictionary alloc ] init];

[dictplist setObject:self.strWeb_id forKey:@”web_id”];

[dictplist writeToFile:plistPath atomically:YES];

15.获取文件夹的大小

-(long long) fileSizeAtPath:(NSString*) filePath{

NSFileManager* manager = [NSFileManager defaultManager];

if ([manager fileExistsAtPath:filePath]){

return [[manager attributesOfItemAtPath:filePath error:nil] fileSize];

}

return 0;

}

16.改变tablevlewcell点击的颜色

 

cell.selectedBackgroundView = [[[UIView alloc] initWithFrame:cell.frame] autorelease];

cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRed:54/255.0f green:110/255.0f blue:100/255.0f alpha:1.0f];

cell.textLabel.highlightedTextColor = [UIColor xxxcolor];  [cell.textLabel setTextColor:color

 

点击后,过段时间cell自动取消选中
[self performSelector:@selector(deselect) withObject:nil afterDelay:0.5f];

– (void)deselect

{

[self.tableVieww deselectRowAtIndexPath:[self.tableVieww indexPathForSelectedRow] animated:YES];

}

17.改变UITableViewStyleGrouped背景颜色

 

self.tableVieww.backgroundColor =[UIColor colorWithPatternImage:[UIImage imageNamed:@”更多背景图.png”]];

self.tableVieww.backgroundView =nil;

18.视图反转

//水平
queuePlayer.transform = CGAffineTransformScale(queuePlayer.transform, 1.0, -1.0);
//垂直    queuePlayer.transform = CGAffineTransformScale(queuePlayer.transform, -1.0, 1.0);
19.改变icon的阴影圆圈,取消图标上的高光

1.进入plist文件    2.在Supported interface orientations 添加  Icon already includes gloss effects  设置为YES              也就是用自己的icon,不用系统的了

20.动态UIlable后添加图片

self.userNameLabel=[[[UILabel alloc]initWithFrame:CGRectMake(60, 7, 220, 20)]autorelease];

self.userNameLabel.textColor= [UIColor blackColor];

self.userNameLabel.text=self.strNamee;

self.userNameLabel.backgroundColor=[UIColor clearColor];

self.userNameLabel.numberOfLines=0;

UIFont *font = [UIFont fontWithName:@”Helvetica-Bold” size:17.0f];

[self.userNameLabel setFont:font];

[self.contentView addSubview:self.userNameLabel];

CGSize size = [self.strNamee sizeWithFont:font constrainedToSize:CGSizeMake(277, 20.0f)];

NSLog(@”kuang %f  “,size.width);

CGRect rect=self.userNameLabel.frame;

rect.size=size;

NSLog(@”321    %f    %f”,rect.size.width,rect.size.height);

[self.userNameLabel setFrame:rect];

//判断男女

UIImageView * imaSex=[[UIImageView alloc]initWithFrame:CGRectMake(self.userNameLabel.frame.size.width+65, 10, 12, 13)];

21.向自定义的cell中传值,*好用 方法在tableview中来调用 如果有参数  直接来传
22.精确时间差
//时间差

– (NSString *)intervalSinceNow: (NSString *) theDate

{

NSDateFormatter *date=[[NSDateFormatter alloc] init];

[date setDateFormat:@”yyyy-MM-dd HH:mm:ss”];

NSDate *d=[date dateFromString:theDate];

NSTimeInterval late=[d timeIntervalSince1970]*1;

NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:0];

NSTimeInterval now=[dat timeIntervalSince1970]*1;

NSString *timeString=@””;

NSTimeInterval cha=now-late;

if (cha/3600<1) {

timeString = [NSString stringWithFormat:@”%f”, cha/60];

timeString = [timeString substringToIndex:timeString.length-7];

timeString=[NSString stringWithFormat:@”%@分钟前”, timeString];

 

}

if (cha/3600>1&&cha/86400<1) {

timeString = [NSString stringWithFormat:@”%f”, cha/3600];

timeString = [timeString substringToIndex:timeString.length-7];

timeString=[NSString stringWithFormat:@”%@小时前”, timeString];

}

if (cha/86400>1)

{

timeString = [NSString stringWithFormat:@”%f”, cha/86400];

timeString = [timeString substringToIndex:timeString.length-7];

timeString=[NSString stringWithFormat:@”%@天前”, timeString];

 

}

[date release];

return timeString;

}

22.按钮在cell上单击第几行

在cell.contentView上:

//获得row

NSInteger row = [[self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]] row];

//获得section

NSInteger row = [[self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]] section];

//获得indexPath

NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[[sender superview] superview]];

直接添加到cell上:
//获得row
NSInteger row = [[self.tableView indexPathForCell:(UITableViewCell *)[sender superview]] row];
//获得section
NSInteger section = [[self.tableView indexPathForCell:(UITableViewCell *)[sender superview]] section];
//获得indexPath
NSIndexPath *indexPath = [self.tableView indexPathForCell:(UITableViewCell *)[sender superview]];
23:判断Home键在哪个方法要执行对应的方法
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(rongTianOrientation:) name:@”UIDeviceOrientationDidChangeNotification” object:nil];

 

– (void) deviceOrientationDidChangeAction:(NSNotification *)note

{

NSInteger currentOrientation = [[note object] orientation];

switch (currentOrientation)  {

case0: {   //未知方向

break;

}

case1: {   //home键向下

break;

}

case2: {   //home键向上

break;

}

case3: {  //home键向左

break;

}

case4: {  //home键向右

break;

}

default:

break;

}

}

24.模拟器不能运行的错误

dyld: Library not loaded: @rpath/SenTestingKit.framework/Versi*****/A/SenTestingKit
Referenced from: /Users/⋯⋯/Application Support/iPhone Simulator/5.0/Applicati*****/F179924C-0EB7-4CCA-88D6-3BA1F68F122D/ILUTU.app/ILUTU
Reason: image not found

 

把SentestingKit。 frameWork 有原来的required改为Optional  就ok

25.还原状态栏

显示原来的状态栏

(1)

[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:YES];

[[UIApplication sharedApplication].keyWindow setFrame:CGRectMake(0, 20, 320, [UIScreen mainScreen].applicationFrame.size.height)];

 

//重新设定标题栏显示的位置

[self.navigationController.navigationBar setFrame:CGRectMake(0, 0, 320, 44)];

(2)

在别的页面[[UIApplication sharedApplication].keyWindow setFrame:CGRectMake(0, 0, 320, [UIScreenmainScreen].applicationFrame.size.height)];

26.获得相册视频的总时间
– (int)getVideopTime:(NSURL * )videourl

{
NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumbernumberWithBool:NO]

forKey:AVURLAssetPreferPreciseDurationAndTimingKey];

AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:videourl options:opts]; //初始化视频媒体文件

int minute = 0, second = 0;

second = urlAsset.duration.value / urlAsset.duration.timescale; // 获取视频总时长,单位秒

NSLog(@”movie duration : %d”, second);

if (second >= 60) {
int index = second / 60;

minute = index;

second = second – index*60;

}

return second;

}

27.视频播放器 循环播放 大小……

(1) MPMoviePlayerController

MPMoviePlayerController *player;

NSURL *url =[NSURL URLWithString:fileName];

player = [[MPMoviePlayerController alloc] init];

player.view.frame = CGRectMake(10, 30, 300    , 225);

player.contentURL = url;

player.repeatMode = MPMovieRepeatModeOne;

player.controlStyle = MPMovieControlStyleEmbedded;

 

player.scalingMode = MPMovieScalingModeAspectFill;   //充满屏幕

[self.view addSubview:player.view];

[player play];

(2).avplayer

 

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:)                                                       name:AVPlayerItemDidPlayToEndTimeNotification

object:plaitem];

}

}];

#pragma mark – Notification Callbacks

– (void)playerItemDidReachEnd:(NSNotification *)notification {
NSLog(@”是跳转*侦吗?  “);

[self.queuePlayer seekToTime:kCMTimeZero];

[self.queuePlayer play];

}

28.sina微博错误返回值格式

http://open.weibo.com/wiki/Error_code

29.ios 获得文件夹的大小

 

//计算文件夹下文件的总大小

-(long)fileSizeForDir:(NSString*)path

{

NSFileManager *fileManager = [[NSFileManager alloc] init];

long size = 0;

NSArray* array = [fileManager contentsOfDirectoryAtPath:path error:nil];

for(int i = 0; i<[array count]; i++)

{

NSString *fullPath = [path stringByAppendingPathComponent:[array objectAtIndex:i]];

 

BOOL isDir;

if ( !([fileManager fileExistsAtPath:fullPath isDirectory:&isDir] && isDir) )

{

NSDictionary *fileAttributeDic=[fileManager attributesOfItemAtPath:fullPath error:nil];

size+= fileAttributeDic.fileSize;

}        else        {

[self fileSizeForDir:fullPath];

}

}

[fileManager release];    return size;    }

30.    谓词过滤

//搜索用谓词过滤数组

NSArray * arrMy=@[@”张2荣三a”,@”李四b”,@”王五a”,@”李流j”,@”荣天321″,@”iOS基地”,@”iOS7″];

NSString  * strg=@”荣”;

NSPredicate * fiecate=[NSPredicate predicateWithFormat:@”SELF CONTAINS %@”,strg];

NSArray * arr3=[arrMy filteredArrayUsingPredicate:fiecate];

NSLog(@”这是我过滤的数组对吗?%@”,arr3);

31.多线程的多种创建

 

//    NSThread * th=[[NSThread alloc]initWithTarget:self selector:@selector(thAction) object:nil];

//    [th start];

 

//[NSThread detachNewThreadSelector:@selector(thAction) toTarget:self withObject:nil];

 

// [self performSelectorInBackground:@selector(thAction) withObject:self];

 

//    NSOperationQueue * operationQueue=[[NSOperationQueue alloc]init];

//    [operationQueue addOperationWithBlock:^{

//        for(int i=0; i<20;i++){

//            NSLog(@”This isThread: %d”,i);

//        }

//    }];

 

//    NSOperationQueue * operationQueue=[[NSOperationQueue alloc]init];

//    //设置线程池中的并发数

//    operationQueue.maxConcurrentOperationCount=1;

//

//    NSInvocationOperation * invocation1=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(threadOne) object:nil];

//    [invocation1 setQueuePriority:NSOperationQueuePriorityLow];

//

//    NSInvocationOperation * invocation2=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(threadTwo) object:nil];

//    [invocation2 setQueuePriority:NSOperationQueuePriorityHigh];

//    [operationQueue addOperation:invocation1];

 

//    [operationQueue addOperation:invocation2];

32.用多线程开启Nstimer提高精确度

//用多线程开启nstimer提高精确度

– (void)mutiThread

{

NSLog(@”Start NStimer”);

@autoreleasepool {

[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];

}

//获得当前的runloop,线程就停在这里

[[NSRunLoop currentRunLoop]run];

NSLog(@”after”);

}

– (void)timerAction:(NSTimer * )timer

{

i++;

NSLog(@”Print NSTimer”);

if (i==5) {

[timer invalidate];

}

}

33.判断此页面是push,还是模态过来的

if (self.presentingViewController) {

NSLog(@”这个是模态过来的!”);}

34。等比例放大缩小视图

UILabel * la=(UILabel * )[self.view viewWithTag:908];

[UIView animateWithDuration:.5 animations:^{

CGAffineTransform transform=la.transform;

transform=CGAffineTransformScale(la.transform, 1.5, 1.5);

la.transform=transform;

} completion:^(BOOL finished) {
CGAffineTransform transform=la.transform;

transform=CGAffineTransformScale(la.transform, 0.5, 0.5);

la.transform=transform;

}];

35.清掉编译文件

~/Library/Developer/Xcode/DerivedData

模拟器清空编译

~/Library/Application Support/iPhone Simulator/

36.改变状态栏的颜色状态

[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];

[[UIApplication sharedApplication] setStatusBarHidden:NO];

37.给自己的项目中添加特殊的标示符号

http://patorjk.com/software/taag/#p=moreopts&h=0&v=1&f=优雅&t=V

38 清空某个文件夹

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *outputURL = paths[0];

[NSFileManager.new removeItemAtPath:outputURL error:nil];

[NSFileManager.new createDirectoryAtPath:outputURL

withIntermediateDirectories:YES

attributes:nil

error:NULL];

39. 在document下创建文件

NSString *writePath=[NSString stringWithFormat:@”%@/%@.txt”,stre,@”aaa”];

NSData *data = [@”” dataUsingEncoding:NSUTF8StringEncoding];//新文件的初始数据,设为空

[[NSFileManager defaultManager] createFileAtPath:writePath contents:data attributes:nil];//创建文件的命令在这里

40.layoutSubviews在以下情况下会被调用:

1、init初始化不会触发layoutSubviews

2、addSubview会触发layoutSubviews
3、设置view的Frame会触发layoutSubviews,当然前提是frame的值设置前后发生了变化
4、滚动一个UIScrollView会触发layoutSubviews
5、旋转Screen会触发父UIView上的layoutSubviews事件
6、改变一个UIView大小的时候也会触发父UIView上的layoutSubviews事件

41.苹果审核加急通道

https://developer.apple.com/appstore/contact/?topic=expedite

51 .美化配置git log

$ git config –global alias.lg “log –color –graph –pretty=format:’%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset’ –abbrev-commit –”
$ git lg
52.查看远程 git
git remote show origin
53.显示远程git仓库
git remote show
54.OC冒泡排序
//   NSMutableArray 自带排序方法

NSMutableArray *lastArrary = [NSMutableArray arrayWithArray:sinceArray];

[lastArrary sortedArrayUsingSelector:@selector(compare:)];

– (NSComparisonResult)compare:(NSNumber *)otherNumber

{

// 设置升 降序
return NSOrderedDescending;

}

// 冒泡排序
NSMutableArray *sinM_Array = [NSMutableArray arrayWithArray:sinceArray];

for (int x = 0; x < sinceArray.count -1; x ++) {

for (int y = x + 1; y < sinceArray.count – 1 -x; y ++) {

int leftNum = [[sinM_Array objectAtIndex:x] intValue];

int rightNum = [[sinM_Array objectAtIndex:y]intValue];

if (leftNum > rightNum) {

[sinM_Array replaceObjectAtIndex:x withObject:[NSNumber numberWithInt:leftNum]];

[sinM_Array replaceObjectAtIndex:y withObject:[NSNumber numberWithInt:rightNum]];

}

}

}
55.前后摄像头的切换

– (AVCaptureDevice *)cameraWithPosition:(AVCaptureDevicePosition)position

{

NSArray *devices = [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];

for ( AVCaptureDevice *device in devices )

if ( device.position == position )

return device;

return nil;

}

– (void)backCarme:(UIButton *)button

{

NSArray *inputs = self.session.inputs;

for ( AVCaptureDeviceInput *input in inputs ) {

AVCaptureDevice *device = input.device;

if ( [device hasMediaType:AVMediaTypeVideo] ) {

AVCaptureDevicePosition position = device.position;

AVCaptureDevice *newCamera = nil;

AVCaptureDeviceInput *newInput = nil;

if (position == AVCaptureDevicePositionFront)

newCamera = [self cameraWithPosition:AVCaptureDevicePositionBack];

else

newCamera = [self cameraWithPosition:AVCaptureDevicePositionFront];

newInput = [AVCaptureDeviceInput deviceInputWithDevice:newCamera error:nil];

[self.session beginConfiguration];

[self.session removeInput:input];

[self.session addInput:newInput];

[self.session commitConfiguration];

break;

}

}

}
56.#pragma mark –获得视频的尺寸
-(CGSize)getImage:(NSURL *)url

{
NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]forKey:AVURLAssetPreferPreciseDurationAndTimingKey];

AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:opts];

CGSize size = [[[urlAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] naturalSize];

return size;

}

57.播放音频

// 导入框架 AudioToolbox.framework

NSString *path = [[NSBundle mainBundle] pathForResource:@”msgcome” ofType:@”wav”];

NSURL *url = [NSURL fileURLWithPath:path];

SystemSoundID soundId;

AudioServicesCreateSystemSoundID((CFURLRef)url, &soundId);

AudioServicesPlaySystemSound(soundId);

【IOS学习之常见问题】 关于IOS证书的那点折腾事1-想要知道的事

█ 证书的种类
通常需要从苹果开发者中心下载两种文件安装在电脑上面才可以进行正式开发:
● 一种是Developer/Distribution Certification(开发/发布证书),这个和电脑系统有关,创建后,只能安装两台电脑,第三台电脑就需要从其他系统把证书导过来,再安装。
该证书的生成需要mac电脑的钥匙串访问工具(Keychain Access.app)生成XXX.certSigningRequest请求证书(包含了mac电脑的信息和用户信息),就是CSR文件。然后在开发者中心去申请iOS App Development/App Store and Ad Hoc Certification。
● 一种是Development/Distribution Provisioning Profiles (开发/发布授权文件),这个主要在真机测试、导出ipa文件、上传到appstore时使用。这个才是真正意义上的签名证书(类似android开发中的android.keystore文件)
该证书的生成需要先在Certificates, Identifiers & Profiles 界面中填写好App IDs(区分不同的app应用的id号,唯一的)和 Devices(区分不同的移动设备的UDID号,唯一的,*多只能绑定100台设备)等资料,还有前面的Certification证书,方可生成。
当然如果生成发布正式版本(App Store Distribution Provisioning Profiles)就不需要Devices,因为所有设备都可以用,而其他证书,需要选择Devices,限制了部分设备可以安装该app。

PS 1:由于Xcode 7取消了真机调试签名的验证,因此开发者可以不用创建调试证书

PS 2:设备的UDID可以通过iTunes查看

█ 证书与电脑的关系
一个苹果开发证书*多只能有2个证书,当你创建第三个证书,就会出现问题,比如前面的app的证书就会失效,此外这里说的2个证书,不包括Apple Push Services推送类型的证书。
● 2个证书如果要开发多个项目怎么办?
和android一样,不同的app是通过bundle id来区分的,因此一个证书可以开发多个app,但是不同app的bundle id要不同。
● 多台电脑同时开发多个项目怎么办?
只能通过两个CSR文件创建两个开发/发布证书(cer文件),也就是说,只能安装在两台电脑上面使用,第三台电脑就没有办法通过安装证书来获取开发的资格了。
那么第三台电脑就需要从前面两台电脑上面把cer证书对应的private key(.p12文件)导出(可以参考【相关资料】中的【 5.iOS证书(.p12)和描述文件(.mobileprovision)申请】),需设置密码。后面操作都一样,都是安装 cer或p12文件,再安装pp( Provisioning Profiles)文件。这样就可以实现一个证书在多台电脑上面开发应用。

PS 1:通过拨打开发者运营中心的客服电话了解到,确实*多只能创建2个发布证书

PS 2:采用provisioning profile+P12真机调试的方式不要求开发者知道appleID以及密码,密码一般放在leader那里

█ 发布证书与推送证书
● 每个开发者账号创建的发布证书的证书都是一样,只不过里面包含的CSR文件(包含了mac信息)内容不同。所以申请后的证书只能在该mac电脑上面安装使用。
● 每个开发者账号可以创建的证书个数是受限的(似乎是2个,目前还没具体测试),但是推送证书不算在这个数字内。
● 推送证书(Apple Push Notification service SSL )主要用来使用推送服务的。创建时,需要绑定App ID号,通常是对建好的App ID号进行编辑时,在里面上传CSR文件生成的。

%title插图%num

● 如果使用*光推送,在苹果开发者中心也不需要配置*光的相关信息,推送证书是需要密码发送推送消息的(把推送证书导出的P12文件时定义的那个密码,P12文件是给服务器使用的)
● *光也不过是对苹果的推送封装了一下而已,不可能脱离苹果的服务的,当然安卓就不是简单的封装了
● 不管是发布证书还是推送证书,过期了,就重新配置下就可以了,不会影响旧的ipa。证书的有效期通常是自创建日起1年有效期。
● 发布证书过期可能会导致app闪退,证书不符,当然也有可能是ios版本升级。正常是不会影响旧的ipa。也不会出现断层等问题,可以直接覆盖安装。
● 推送证书过期可能会导致推送服务不能正常使用。
● 推送证书的一些信息,我们也可以通过*光推送官网了解一些信息:iOS 证书设置指南 – *光文档

█ 查看证书信息的方式
● 普通方式
首先我们登陆 developer.apple.com ->点击右上角的导航Account->点击Certificates, Identifiers & Profiles -> 点击左侧栏 Certificates 下的Development -> iOS Certificates (Development):

● 特殊方式
直接登陆
https://developer.apple.com/account/ios/certificate/,如果未登录,可以登陆后,再直接访问该网址。
%title插图%num
也可以在mac电脑的钥匙串访问(keychain access.app)中查看到是否安装,以及过期时间,双击证书可以看到详情:
%title插图%num

█ 关于iOS证书过期的问题
▼ 常见问题解疑:
● Apple ID账号欠费会导致App被下架。
● 证书过期不会影响已发布的产品。未发布的产品也不会有影响。
● 证书过期导致推送会出问题,更新推送证书也不用下架重新发布,可以重新生成,只要保证 developer.apple.com 中那个 APP ID 的推送证书和推送服务器上的一致即可。
● 证书过期,重新创建证书,可能导致之前上线的没办法覆盖安装,签名不一样就没法覆盖(估计是新建PP文件导致的)
● 证书的有效期不能修改,只能重新下载。
● iOS的推送证书也有过期时间的限制的,过期后就需要开发者在苹果官网上重新生成一个证书。
▼ 解决方法:
● 先revoke你的Certificate,重新生成一个新的。
● Edit一下你的pp证书,选择新的Certificate。
● 下载覆盖之前的证书,就可以了。

PS 1:亲测过,证书过期后,已经上架appstore的app和已经安装在用户设备的app都可以正常使用。但是在mac电脑上面的证书就不能使用,导致开发者不能继续开发。因此重新生成的证书,编辑旧的PP文件(只更新旧的PP文件中的证书信息),再给mac电脑安装cer文件和pp文件,重新导出的ipa文件,安装时,不需要卸载旧的app,可以直接覆盖旧的app。

PS 2:Certificate的有效期为一年,自创建证书开始算起,而PP文件的有效期是同它关联的证书的有效期一致,证书过期了,所有和他绑定的PP文件也一起过期。

█ 安装pp( Provisioning Profiles)文件的建议:
● 进入PP文件的安装路径:
启动Xcode -> 菜单栏Perferences… ->Accounts ->View Details…->iOS Team Provisioning Profile:*->右键 Show in Finder
%title插图%num%title插图%num

安装路径为/Users/用户名/Library/MobileDevice/Provisioning Profiles。
● 命名法则:
其中一大堆序列码命名的PP文件,是通过双击PP文件自动安装的,它是用PP文件中的UUID散列去命名。

%title插图%num

这里我们也可以看到PP文件中包含了 证书、App ID、设备等信息。
● 如何自定义命名:
这个就是我们推荐的,当然也无所谓,因为可能或者你基本都没有用到,想要自定义命名,当然不能直接在这个文件夹中进行重命名,否则会出现问题。
方法一:可以直接将PP文件拷贝或拖曳到该路径(/Users/用户名/Library/MobileDevice/Provisioning Profiles)下,那么也会自动安装并以PP文件原文件名保存在该路径下。
方法二,右键PP文件,选择以iTunes方式打开。
方法三,打开应用程序中的iTunes,将PP文件拖曳到iTunes界面中。
后面两种方法比较方便,不需要找到PP文件的安装路径。因为该路径是隐藏路径,正常方法方法也不好找到。

PS 1:PP文件的真实文件名为UUID,所以双击PP文件来安装会自动用UUID来命名。
PS 2:iOS设备上每个应用程序也对应一个UUID固定值,当应用被卸载重装后UUID会被重新赋值,和之前就不同了。
PS 3:如果要删除PP文件,是可以直接在该文件夹中删除对应的PP文件,也可以在Xcode中删除

█ 关于iTunes的两个用法
● 查看设备的UDID序列号
UDID类似于人类的身份证,用来唯一区分不同的机器设备,所以理论上,每个UDID都是不重复的。UDID正常是是40位固定长度的16进制数组成。
%title插图%num
当然IOS开发者也可以通过代码来获取。还可以通过XCode 7.3.1 的Window -> Devices获取:

%title插图%num

另外也可以通过爱思助手等软件,直接检测到设备标识(UUID):

%title插图%num
● 安装ipa软件
通过iTunes来安装ipa软件,

%title插图%num

当然也可以通过91助手、二维码扫描下载app(先上传到蒲公英上或风起云飞「fir.im」等)等方式来安装ipa文件。

%title插图%num
PS 1:使用iTunes来安装ipa发现一直在同步中,可能和Apple ID账号有关吧,查看网上很多资料,安装方法和我说的是一致的。

PS 2:91助手在IOS7.0和IOS10.0上面安装证书已过期开发的ipa文件会失败,直接在appstore上面下载是可以正常安装。
█ 重要的知识点
● 开发证书可以不用创建。
● 发布证书可以随时删除,随时新建,就是多花点时间,所以证书过期什么的,都不是问题。
● PP文件(授权文件)*好创建后,不要删除,等证书新建后,直接Edit更新下证书即可(或者实际上,都可以直接删除新建也都没事,不过还未测试下,是否会导致app升级时,可否直接覆盖问题)
● 不要让Apple ID账号过期,否则导致app下架之后,等下次上传就可能导致链接不同,如果你的产品使用二维码扫描下载的话,就需要重新制作二维码,如果有纸质包装盒或者说明书,就更麻烦了

█ 相关资料:
为了研究这些证书问题,在网上查找了一些资料,也在这里分享下,作为单独的一篇文章:【IOS学习之常见问题】 关于IOS证书的那点折腾事2-百科证书
如果本文说明的还不够明白,或者想要多了解一些知识,就可以参考这些资料。
后期会陆续增加一些相关内容:
【IOS学习之常见问题】 关于IOS证书的那点折腾事3-账号续费

iOS 3D卡片式轮播

效果:

%title插图%num

参考UITableView的UITableViewDataSource和UITableViewDelegate两个方法实现;支持五险轮播,可以加载本地图片,也可以加载网络图片,可以根据自己的需求自定义
Demo地址

UITableViewDelegate

1 /**
2  *  当前显示cell的Size(中间页显示大小)
3  *
4  *  @param flowView <#flowView description#>
5  *
6  *  @return <#return value description#>
7  */
8 – (CGSize)sizeForPageInFlowView:(HQFlowView *)flowView;
9
10 /**
11  *  滚动到了某一列
12  *
13  *  @param pageNumber <#pageNumber description#>
14  *  @param flowView   <#flowView description#>
15  */
16 – (void)didScrollToPage:(NSInteger)pageNumber inFlowView:(HQFlowView *)flowView;
17
18 /**
19  *  点击了第几个cell
20  *
21  *  @param subView 点击的控件
22  *  @param subIndex    点击控件的index
23  *
24  *  @return <#return value description#>
25  */
26 – (void)didSelectCell:(HQIndexBannerSubview *)subView withSubViewIndex:(NSInteger)subIndex;

 

UITableViewDataSource

1 /**
2  *  返回显示View的个数
3  *
4  *  @param flowView <#flowView description#>
5  *
6  *  @return <#return value description#>
7  */
8 – (NSInteger)numberOfPagesInFlowView:(HQFlowView *)flowView;
9
10 /**
11  *  给某一列设置属性
12  *
13  *  @param flowView <#flowView description#>
14  *  @param index    <#index description#>
15  *
16  *  @return <#return value description#>
17  */
18 – (HQIndexBannerSubview *)flowView:(HQFlowView *)flowView cellForPageAtIndex:(NSInteger)index;

iPhone 检测 iPhone X 设备的几种方式和分辨率终*指南

文章目录

    • iPhone 屏幕分辨率终*指南
    • 适配新的 iPhone X 设备
    • 检测 iPhone X/XS/XR 设备的几种方式
        • 方式一:通过获取设备的 device model 来判断
        • 方式二:通过获取屏幕的宽高来判断
        • 方式三:通过底部安全区域的高度来判断
        • 方式四:通过是否支持 FaceID 判断
        • 方式五:通过 UIStatusBar 的高度判断
    • 结语
    • 原文链接
    • 参考连接本文是我们前两天发的两条小集的汇总,主要包括三部分:
      1. iPhone 屏幕分辨率总结
      2. 如何适配新的 iPhone X 设备
      3. 检测设备是否为 iPhone X/XS/XR 的几种方式

      iPhone 屏幕分辨率终*指南

      上周,苹果发布了三款新的 iPhone 设备,它们的屏幕数据分别如下:

      1. iPhone XS: 5.8 英寸,375pt * 812pt (@3x);
      2. iPhone XR: 6.1 英寸,414pt * 896pt (@2x);
      3. iPhone XS Max: 6.5 英寸,414pt * 896pt (@3x);

      在国外的 PaintCode 网站上,有一篇文章《The Ultimate Guide To iPhone Resolutions》整理了包括从*代 iPhone 到*新发布的 iPhone XS Max 等所有 iPhone 设备的屏幕数据,包括:开发尺寸(points)、物理尺寸(pixels)以及实际渲染像素、1倍/2倍/3倍模式等,如图 1 所示(建议大图查看更加清晰)。%title插图%num

    •  

      从图中数据我们可以总结以下几点:

      1. 5.8 英寸的 iPhone X/XS 与 6.1 英寸的 iPhone XR 和 6.5 英寸的 iPhone XS Max 的屏幕宽高比是一致的,约为 0.462;
      2. iPhone X/XS 的屏幕宽度(开发尺寸)与 4.7 英寸的 iPhone 8 相同,都为 375pt,只是在高度上增加了
        145pt;
      3. iPhone XR 和 iPhone XS Max 的屏幕宽度(开发尺寸)与 5.5 英寸 iPhone 8 Plus 相同,都为
        414pt,只是在高度上增加了 160pt;

      因此,设计师在出图时,仍然可以以 iPhone 8 和 iPhone 8 Plus 的屏幕宽度为基准分别进行 UI 布局,而对于不同高度的屏幕只要在纵向上进行内容延伸即可。


      适配新的 iPhone X 设备

      此外,我们发现,对于未进行新屏幕尺寸适配的工程,直接编译,在新设备 iPhone XR 和 iPhone XS Max 上运行,它们是以放大模式自动适配的(以 5.8 寸的 iPhone X 屏幕为基准等比例放大),此时在代码中获取到的屏幕宽高都为 375pt * 812pt。

      那么如何正确适配新的屏幕尺寸呢?

      1. 如果你的工程是以 LaunchScreen.storyboard 作为启动页,则只需要在 Xcode 10 下重新编译工程即可;
      2. 如果你的工程是通过配置 Assets.xcassets 里的 LaunchImage 不同尺寸的启动图片作为启动页,则你需要新增两张
        828px * 1792px 和 1242px * 2688px 分辨率的图片,如图 2 所示。
        %title插图%num

      检测 iPhone X/XS/XR 设备的几种方式

      *后,我们如何在代码中判断当前设备是否为 iPhone X 呢?

      备注:这里所说的 iPhone X 泛指上述介绍的屏幕大小为 5.8、6.1、6.5 英寸三种尺寸,且带有顶部刘海和底部操作条的 iPhone 设备。

      一开始我们采用了一种比较简便的方法:通过获取屏幕的高度,判断是否等于 812.0 或 896.0,代码如图 3 所示。
      %title插图%num

      但该方法存在小瑕疵,需要考虑一下两点:

      1. 当 App 支持横竖屏切换时,在横屏模式下也能够正确判断;
      2. 在模拟器中调试时,能够正确判断当前所选则的模拟器类型是不是 iPhone X;

      因此,我们重新整理一下目前所了解到的几种检测设备是否为 iPhone X 的方式,供大家参考,不足之处欢迎补充。

      方式一:通过获取设备的 device model 来判断

      每一台 iOS 设备都有对应的硬件编码/标识符,称为 device model 或者叫 machine name,我们可以通过如下两种方法来获取 device model/machine name。
      %title插图%num

      例如,去年发布的*代 iPhone X 对应的 device mode 为 iPhone10,3 和 iPhone10,6,而今年*新发布 iPhone XS 对应 iPhone11,2,iPhone XS Max 对应 iPhone11,4 和 iPhone11,6,iPhone XR 对应 iPhone11,8,完整的 device mode 数据参考这里:

      • https://www.theiphonewiki.com/wiki/Models

      不过需要注意的是,上述两种获取 device model 的方法在模拟器中运行得到的值为 i386 或 x86_64,因此在模拟器中我们可以通过如下方式正确获取模拟器所对应的 device model:

      // 获取模拟器所对应的 device model
      NSString *model = NSProcessInfo.processInfo.environment[@"SIMULATOR_MODEL_IDENTIFIER"];
      

      综上,我们可以通过判断 device model 是否为 “iPhone10,3” 和 “iPhone10,6” 或者以 “iPhone11,”(新设备)开头,来检测设备是否为 iPhone X,完整代码如下:%title插图%num

    •  

      %title插图%num方式二:通过获取屏幕的宽高来判断

      正如我们前面讲到,目前 iPhone X 设备的屏幕宽高对应的开发尺寸只有两种,分别为 375pt * 812pt 和 414pt * 896pt,因此我们可以根据屏幕的高度来判断设备是否为 iPhone X。但是此时需要考虑设备处于横屏或者竖屏的情况,这两种情况的宽高刚好是相反的(当然,如果你的 App 不用支持横屏的情况,就相对比较简单了)。

      在 UIDevice 中提供了一个 orientation 属性用于获取设备的方向(横向、竖向、或者水平),一开始我们想着先通过这个属性判断设备处于横屏或者竖屏,然后分别取其对应的屏幕宽度(横屏下)或者高度(竖屏下)来判断,但是当这个属性的值为 FaceUp 或者 FaceDown(即设备放在水平面上),我们是无法知道此时设备是处于横屏还是竖屏的。

      后面我们想了一个简便的方法,即获取屏幕的宽度和高度,取较大一方进行比较是等于 812.0 或 896.0,代码如下:
      %title插图%num

      方式三:通过底部安全区域的高度来判断

      在去年 iPhone X 发布后,为了适配顶部的浏览和底部的操作条,苹果在 iOS 11 上引入安全区域概念,建议开发者在安全区域内进行 UI 布局,因此我们可以获取屏幕 keyWindow 的 safeAreaInsets 值来判断设备是否 iPhone X。

      iPhone X 在竖屏下,keyWindow 的 safeAreaInsets 值为:

      {top: 44, left: 0, bottom: 34, right: 0}
      

      而在横屏下,其值为:

      {top: 0, left: 44, bottom: 21, right: 44}
      

      因此,我们可以比较 safeAreaInsets 的 bottom 是否等于 34.0 或者 21.0 来判断设备是否为 iPhone X,因为其他设备对应的 bottom 横竖屏下都为 0,代码如下:
      %title插图%num

      不过该方式有个不足是,必须在 AppDelegate 的 didFinishLaunchingWithOptions 回调中等 keyWindow 初始化之后才能正确判断。

      方式四:通过是否支持 FaceID 判断

      由于目前只有 iPhone X 设备支持 FaceID,因此我们也可以通过判断设备是否支持 FaceID 来判断,代码如下:%title插图%num不足:如果用户禁用 canEvaluatePolicy:error: 方法的使用将无法正确判断,而且在也不适用于模拟器中的判断。

      方式五:通过 UIStatusBar 的高度判断

      在 iPhone X 之前,所有 iPhone 设备的 StatusBar(状态栏)高度都为 20pt,而 iPhone X 的为 44pt,因此我们可以通过获取状态栏的高度判断是否等于 44.0 来检测设备是否为 iPhone X,代码如下:
      %title插图%num

      不足:该方法只适用于竖屏且显示状态栏的情况下才能正确检测,而在横屏模式下,或者 App 隐藏导航栏时,获取到的状态栏高度都为 0(statusBarFrame 的值为 CGRectZero),就无法判断了。

      你是否有其他判断方式呢?欢迎补充~


      结语

      *后,*大部分场景,我们需要检测设备是否为 iPhone X 是为了适配顶部的刘海区域和底部的操作条区域,但是这里我们更推荐通过 Auto Layout 结合 Safe Area 进行 UI 布局,以适应越来越复杂的屏幕状况。

iOS 开发之 In-App Purchase 对接教程 (一)

很久之前就想出一篇IOS内付费的教程,但是一查网上的教程实在太多了,有的写得真的蛮不错的,就心想算了,于是就保存在草稿箱了。至于为什么写完它呢!真是说来话长,*近公司有个项目经理跑来问我有关苹果内付费相关的细节,跟他聊了半天,从项目对接苹果官方支付接口聊到了如何查看App收益,*后终于使他有了一些眉目,但是悲催的是还要我继续去跟他们项目的程序员讲解(真是疯了),所以我就决定给他们项目写一个内购的文档,所以我顺便把这篇博客完成吧!

 

首先进入苹果的ItunesConnection(https://itunesconnect.apple.com)点击左上角的加号新建一个App应用,点击后该网站会弹出一个信息编辑框,大家只要将上面的信息填充完毕点击save即可在苹果的app平台上拥有一个属于自己的App。

%title插图%num

在套装ID的上,需要提前为该App申请一个AppID以及BundleID,只要是申请成功了就会在选择列表中显示出来。如果有人有疑问如何申请,请看我之前那一篇推送的博客,里面有详细的步骤。

这里顺便多说一句这个ItunesConnect是用来干嘛的,它是苹果公司给个人或企业提供管理自己App的一个平台。在这个平台上开发者可以新建,删除和管理自己的App应用,开发者可以根据需求对App应用进行上架与下架,编辑App信息,生成测试app所需的信息,例如账号,邀请码等,还有就是我们今天要讲的内付费功能。当然啦,他的功能可不止我讲的这些,我大致说一下这个平台的作用,如果你经常跟它打交道的话就会慢慢熟悉了。

 

接下来,我就来为大家演示一下如何添加付费道具,首先打开iTunesConnect,显示如下页面:

%title插图%num

选择红圈所圈起来的选项,然后将里面的相关信息补充完毕,如果缺少这一步,内购功能是不会成功的。

假如你已经完成了上述相关银行账户的设置,就点击你的App,选择上面标题栏中的”App 内购买项目”

%title插图%num

随后点击左上角的 “create new”选项,如下图所示,进入到下一个界面:

%title插图%num

 

这个界面是让你选择消费道具的种类,现在改版的网站是有简体中文翻译的,所以不像以前打开一看都不知道选哪一个,甚至都不知道每个代表的什么意思(比如我*次遇到的时候,在领导面前真是囧)。它的种类分为如下几种:

%title插图%num

 

一般对项目来说大多数都是选择“消耗型项目”这个种类,比如游戏中购买金币,宝石balabala~之类的,选中之后就会到这个界面中来:

%title插图%num

在上图所示的编辑框中输入,商品名称,产品ID以及价格等级,在这边说明一下:

1.商品名称根据你的消费道具的实际意义来说明,比如“100颗宝石”,“100金币”等。

2.产品ID是比较重要的,由项目自定义,只要唯一即可,像我一般都是用App的bundleID加一个后缀来表示,这样既跟项目关联又具有唯一性。

3.价格等级的话“查看价格表”中有对应的说明,可以对照着表中每个国家的货币价格与等级来选择。

 

我们继续,在这个网页的接下来部分如图所示:

%title插图%num

选择添加语言选项,弹出一个编辑页面:

%title插图%num

点击save保存,则会在界面上显示成如下:

%title插图%num

*后一步就是点击“选取文件”提交一张苹果它指定像素(640*920)的商品图片,当他上传完毕后点击“save”按钮,我们这第二部分就大工告成了。提交的商品*后会在内购的页面上显示为如图:

%title插图%num

这个图是我在已经发布的app上面截取的,添加了3个商品,已经是通过的的状态了(显示绿色),当您刚提交的时候,因为通过苹果的审查需要一段时间所以会显示黄色的等待状态,所以不必担心是不是商品编辑错了。如图:

%title插图%num

这部分,我主要给大家演示一下,如何申请测试账号,利用苹果的沙盒测试环境来模拟AppStore的购买流程。

在ItunesConnect中选择“用户和职能”选项~

%title插图%num

随后在左上角的选项中选择沙盒测试者,点击左上角的加号图标增加一位测试者,如图:

%title插图%num

编辑好相应的内容,点击保存,就创建了一个测试账号,是不是很简单啊!当然这个账号如果你忘记了密码可以重新生成一个,无关紧要。

顺带多句嘴,不要在正式的appstore上面用沙盒测试的账号来登录,千万要牢记在心,此账号只用于测试环境下~

 

接下来就是代码部分啦~

1.首先在项目工程中加入“storekit.framework”,加入头文件#import <StoreKit/StoreKit.h>

2.在.h文件中加入“SKPaymentTransactionObserver,SKProductsRequestDelegate”监听机制

下面贴上内购的核心代码,就几个函数,我在这边就不在做更多详细的解释了,各位看官可以运行跑一下就一目了然了。

.h文件

 

//
// PaymentViewController.h
// IAPPayTest
//
// Created by silicon on 14-10-28.
// Copyright (c) 2014年 silicon. All rights reserved.
//

#import <UIKit/UIKit.h>

#import <StoreKit/StoreKit.h>

@interface PaymentViewController : UIViewController<SKPaymentTransactionObserver,SKProductsRequestDelegate>

@property (strong, nonatomic) IBOutlet UITextField *productID;

@property (strong, nonatomic) IBOutlet UIButton *purchase;

– (IBAction)purchaseFunc:(id)sender;

@end
.m文件

 

 

//
// PaymentViewController.m
// IAPPayTest
//
// Created by silicon on 14-10-28.
// Copyright (c) 2014年 silicon. All rights reserved.
//

#import “PaymentViewController.h”

@interface PaymentViewController ()

@end

@implementation PaymentViewController

– (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}

– (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.

[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
self.productID.text = @”com.games.ztyxs.product_point.1″;
}

– (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}

– (IBAction)purchaseFunc:(id)sender {
NSString *product = self.productID.text;
if([SKPaymentQueue canMakePayments]){
[self requestProductData:product];
}else{
NSLog(@”不允许程序内付费”);
}
}

//请求商品
– (void)requestProductData:(NSString *)type{
NSLog(@”————-请求对应的产品信息—————-“);
NSArray *product = [[NSArray alloc] initWithObjects:type, nil];

NSSet *nsset = [NSSet setWithArray:product];
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:nsset];
request.delegate = self;
[request start];

}

//收到产品返回信息
– (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response{

NSLog(@”————–收到产品反馈消息———————“);
NSArray *product = response.products;
if([product count] == 0){
NSLog(@”————–没有商品——————“);
return;
}

NSLog(@”productID:%@”, response.invalidProductIdentifiers);
NSLog(@”产品付费数量:%d”,[product count]);

SKProduct *p = nil;
for (SKProduct *pro in product) {
NSLog(@”%@”, [pro description]);
NSLog(@”%@”, [pro localizedTitle]);
NSLog(@”%@”, [pro localizedDescription]);
NSLog(@”%@”, [pro price]);
NSLog(@”%@”, [pro productIdentifier]);

if([pro.productIdentifier isEqualToString:self.productID.text]){
p = pro;
}
}

SKPayment *payment = [SKPayment paymentWithProduct:p];

NSLog(@”发送购买请求”);
[[SKPaymentQueue defaultQueue] addPayment:payment];
}

//请求失败
– (void)request:(SKRequest *)request didFailWithError:(NSError *)error{
NSLog(@”——————错误—————–:%@”, error);
}

– (void)requestDidFinish:(SKRequest *)request{
NSLog(@”————反馈信息结束—————–“);
}

//监听购买结果
– (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transaction{
for(SKPaymentTransaction *tran in transaction){

switch (tran.transactionState) {
case SKPaymentTransactionStatePurchased:
NSLog(@”交易完成”);

break;
case SKPaymentTransactionStatePurchasing:
NSLog(@”商品添加进列表”);

break;
case SKPaymentTransactionStateRestored:
NSLog(@”已经购买过商品”);

break;
case SKPaymentTransactionStateFailed:
NSLog(@”交易失败”);

break;
default:
break;
}
}
}

//交易结束
– (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@”交易结束”);

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}

– (void)dealloc{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[super dealloc];
}

@end
代码就这么多,到这边我们的IOS内购教程就接近尾声了,在测试的时候还有几点因素要注意一下:

 

1.沙盒环境测试appStore内购流程的时候,请使用没越狱的设备。

2.请务必使用真机来测试,一切以真机为准。

3.项目的Bundle identifier需要与您申请AppID时填写的bundleID一致,不然会无法请求到商品信息。

讲了这么多,附上几张测试截屏给大家展示一下:

请求商品时的打印日志:

%title插图%num

交易成功后:

%title插图%num

手机截屏:

要求输入AppStore帐密,使用测试生成的即可:

%title插图%num

确定购买:

%title插图%num

交易完成:

%title插图%num

当我们的交易完成后还要去appstore 上面去验证票据信息是否正确,这样我们才可以给玩家发放道具,apple官方文档:

//交易结束
– (void)completeTransaction:(SKPaymentTransaction *)transaction{
NSLog(@”交易结束”);
//交易验证
NSURL *recepitURL = [[NSBundle mainBundle] appStoreReceiptURL];
NSData *receipt = [NSData dataWithContentsOfURL:recepitURL];

if(!receipt){

}

NSError *error;
NSDictionary *requestContents = @{
@”receipt-data”: [receipt base64EncodedStringWithOptions:0]
};
NSData *requestData = [NSJSONSerialization dataWithJSONObject:requestContents
options:0
error:&error];

if (!requestData) { /* … Handle error … */ }

//In the test environment, use https://sandbox.itunes.apple.com/verifyReceipt
//In the real environment, use https://buy.itunes.apple.com/verifyReceipt
// Create a POST request with the receipt data.
NSURL *storeURL = [NSURL URLWithString:@”https://buy.itunes.apple.com/verifyReceipt”];
NSMutableURLRequest *storeRequest = [NSMutableURLRequest requestWithURL:storeURL];
[storeRequest setHTTPMethod:@”POST”];
[storeRequest setHTTPBody:requestData];

// Make a connection to the iTunes Store on a background queue.
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:storeRequest queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError) {
/* … Handle error … */
} else {
NSError *error;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (!jsonResponse) { /* … Handle error …*/ }
/* … Send a response back to the device … */
//Parse the Response
}
}];

[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
}
好了,所有的内购流程基本上讲完了,原谅我在图片上的涂抹,因为关系到产品的敏感词汇所以希望大家能够不介意。赶这篇博客的时间比较匆忙,如果有童鞋还有什么疑问或者我写的那个地方不对欢迎私信我或者评论,我会在*时间回复。谢谢~

iOS 开发之实现 App 消息推送(*新)

好久没有写过博客啦,今天就由本菜鸟给大家做一个简单的IOSApp消息推送教程吧!一切从0开始,包括XCode6, IOS8, 以及苹果开发者中心*新如何注册应用,申请证书以及下载配置概要文件,相信很多刚开始接触ios的人会很想了解一下。(ps:网上看了一下虽然有很多讲述推送的好教程,我也是看着一步步学会的,但是这些教程的时间都是去年或者更早时期的,对引导新手来说不是很合适)

*部分
首先*步当然是介绍一下苹果的推送机制(APNS)咯(ps:其实每一篇教程都有),先来看一张苹果官方对其推送做出解释的概要图。
%title插图%num

Provider是给你手机应用发出推送消息的服务器,而APNS(Apple Push Notification Service)则是苹果消息推送服务器。你本地的服务器当需要给应用推送一条消息的时候,先要将消息发出到苹果推送服务器,然后再由苹果推送服务器将消息发到安装了该应用的手机。

接下来再看一张解释图:
%title插图%num

根据上图的逻辑我来给大家解释一下:

1.你的IOS应用需要去注册APNS消息推送功能。

2.当苹果APNS推送服收到来自你应用的注册消息就会返回一串device token给你(很重要)

3.将应用收到的device Token传给你本地的Push服务器。

4.当你需要为应用推送消息的时候,你本地的推送服务器会将消息,以及Device Token打包发送到苹果的APNS服

5.APNS再将消息推送给目的iphone

第二部分
1.从证书颁发机构颁发证书

打开你mac的钥匙串访问: 然后点击钥匙串访问%title插图%num

%title插图%num

%title插图%num随后它会弹出一个窗口用户电子邮件信息就填写你苹果开发者账号的名称即可(应该是一个邮件名称),点击保存到磁盘的选

项,点击继续,显示如下:

%title插图%num

点击存储,文件名为:CertificateSigningRequest.certSigningRequest 随后将他放在一个文件夹中我们取名push吧!

 

第三部分
访问苹果开发者网址:https://developer.apple.com/

%title插图%num
选中MemberCenter选项,进入登陆页面,用你的苹果开发者账号登陆,过一会网页就会自动跳转到下图。

%title插图%num

点击红色所选部分内容进行下一步的操作。

%title插图%num

选择Certificates选项,设置证书,如图所示先解释一下:

%title插图%num

Development选项的作用顾名思义就是用来作为开发使用的证书,Production选项则是用来发布产品使用的,名称很陌生是不是,之

前的开发者网页是没有这一选项的,可能是苹果把他修改了,用这个名称更加能让人理解吧(字面上解释就是产品么)。两个选项生

成证书的步骤是一样的,现在我们使用开发者的选项进行证书的制作,步骤如下:选择Development选项
%title插图%num

点击上面的加号选项,%title插图%num

选择APNS选项(开发么当然是在沙盒环境下了,模拟真实情况),然后Continue%title插图%num

这个AppID我们在下一部分讲如何生成,现在我用的是已经生成好的一个应用ID,继续Continue

%title插图%num

这边就要选择在钥匙串访问环节下载下来的CertificateSigningRequest.certSigningRequest文件了,选择并生成:

%title插图%num

点击下载,得到aps_development .cer,保存到push文件中去。

 

第四部分

新建一个AppID,选择网页上的AppIDs,然后点击右上角的 “加号”

%title插图%num

App的取名只要按照苹果要求的就可以了

%title插图%num

然后BundleID是比较重要的,在提交审核以及测试(苹果的TestFlight)和付费环节都需要用到,也只需按照苹果要求来写就好了。

%title插图%num

接下来就是对你的应用需要使用苹果的哪些服务进行选择就行了,例如广告,游戏中心,推送,付费等等情况。

%title插图%num

*后选择“Submit”选项,在下一个界面中选择“done”选项,这样我们设置AppID的步骤我们就完成了。

 

第五部分:生成Provisioning Profiles

这个配置概要文件分为两种,一种是为开发使用的,还有一种则是为发布到appStore上面。

%title插图%num

创建发布版的ProvisioningProfile与开发版的流程相同,点击Development然后点击右上角的加号

%title插图%num
会进入选择何种配置概要文件的界面:

%title插图%num

我们现在时测试,所以选择“IOS App Development”的选项,在下面的Distribution发布选项中有两个选择,“App Store”以及

“Ad hoc”,你可以根据下面的描述选择你发布所需的选项。点击Continue进入下一步。

%title插图%num

选择你上一步创建的AppID,点击Continue 进行下一步

%title插图%num

选择你的开发者账号,Continue进行下一步

%title插图%num

在这一步上选择你的设备(你只有在这一步上勾选了你的设备,你才能在设备上用这个签名进行调试)。关于如何将你的设备号添加

进去也是非常简单的,选择左侧的”Devices”,然后点击右上角的加号,在随后出来的页面上添加你设备的UUID(在XCode中可以查

看到)以及name( 可以随便取,自己看的懂就行)然后Register一下,照着流程走到*后一步就完成了。

好咱们继续回到上面的Provisioning Profile配置环节,当你选好了你的设备后点击“Continue”进入下一页,

%title插图%num

输入一个文件名(*好是起的能看懂是干嘛的,当然也可以随便起),点击“Generate”进入下一个页面,在这个页面中就会有一个下

载按钮让你下载这个文件,我们把它下载下来放在Push文件夹中。

 

第六部分

好了,前期的准备工作都已经做完了,现在让我们开始推送吧!(吼吼)

首先双击我们生成的 “aps_development .cer” 文件,进入钥匙串访问,找到我们的专用秘钥(根据在第二部分中从证书机构颁发证书操作中填写的常用名)

%title插图%num

我在第二部分填写的是“silicon”,由于换了一台mac之前安装的没有了,之前没有截图,所以随便找了个图给大家看一下,凭大家的聪明才智应该不难理解吧。

然后右击导出,会弹出如下所示的图。
%title插图%num

将他存储到push文件夹中,命名为“push.p12”,在这一步中导出会让你输入密码并验证,你可以自定义一个密码,例如abc123

现在push文件夹中应该有几个文件“aps_development .cer” ,”push.p12″,“CertificateSigningRequest.certSigningRequest”以及

刚才下下来的配置概要文件。

 

接下来我们打开终端将他们生成.pem文件

1.把aps_development .cer文件生成.pcm文件,cd到push文件夹下

%title插图%num

2.把push.p12文件生成为.pem文件

%title插图%num

上边输入的密码则是你导出证书所设的密码,即abc123.接着还会让你输入.pem文件的密码,还是使用abc123好了,防止混淆。

这样我们在push文件夹中就又得到了两个文件,PushChatCert.pem和PushChatKey.pem。

 

3.把PushChatCert.pem和PushChatKey.pem合并为一个pem文件,

%title插图%num

在push文件夹中又多了一个ck.pem文件,以上我们把需要使用的文件都准备好了

 

接下来就要测试一下啦,是不是很激动~

为了测试证书工作的状况,我们可以使用“telnet gateway.sandbox.push.apple.com 2195”来检测一下,如果显示下图则表示成功了。

%title插图%num

然后,我们使用我们生成的证书和私钥来设置一个安全的链接去链接苹果服务器

在终端输入如下命令:openssl s_client -connect gateway.sandbox.push.apple.com:2195 -cert PushChatCert.pem -key PushChatKey.pem

需要输入密码(abc123 我们刚才所设置的)。

然后他会返回一系列的数据,这里我就粘贴一部分啦:

 

CONNECTED(00000003)

depth=1 /C=US/O=Entrust, Inc./OU=www.entrust.net/rpa is incorporated by reference/OU=(c) 2009 Entrust, Inc./CN=Entrust Certification Authority – L1C

verify error:num=20:unable to get local issuer certificate

verify return:0

Certificate chain

。。。。。(省略)

。。。。。(省略)

。。。。。(省略)

 

Start Time: 1416389389

Timeout   : 300 (sec)

Verify return code: 0 (ok)

测试就到这里啦。。。

 

第七部分

1.建立推送项目

 

//
// AppDelegate.m
// TestPushNotifiy
//
// Created by silicon on 14-10-30.
// Copyright (c) 2014年 silicon. All rights reserved.
//

#import “AppDelegate.h”

@implementation AppDelegate
@synthesize mainView = _mainView;

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
{
//IOS8
//创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];

[application registerUserNotificationSettings:notiSettings];

} else{ // ios7
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeSound |UIRemoteNotificationTypeAlert)];
}

self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];

self.mainView = [[MainViewController alloc] initWithNibName:@”MainViewController” bundle:nil];
self.window.rootViewController = self.mainView;
return YES;
}

– (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
NSLog(@”—Token–%@”, pToken);
}

– (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{

NSLog(@”userInfo == %@”,userInfo);
NSString *message = [[userInfo objectForKey:@”aps”]objectForKey:@”alert”];

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”提示” message:message delegate:self cancelButtonTitle:@”取消” otherButtonTitles:@”确定”, nil];

[alert show];
}

– (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{

NSLog(@”Regist fail%@”,error);
}

– (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}

– (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}

– (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}

– (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}

– (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}

@end
在appdelegate.m中加入以上代码,

 

 

 

if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
{
//IOS8
//创建UIUserNotificationSettings,并设置消息的显示类类型
UIUserNotificationSettings *notiSettings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeAlert | UIRemoteNotificationTypeSound) categories:nil];

[application registerUserNotificationSettings:notiSettings];

} else{ // ios7
[application registerForRemoteNotificationTypes:(UIRemoteNotificationTypeBadge |UIRemoteNotificationTypeSound |UIRemoteNotificationTypeAlert)];
}

由于ios8的推送跟ios7及以下的不一样,所以需要加判断来注册消息推送。

函数:

 

– (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)pToken{
NSLog(@”—Token–%@”, pToken);
}
会接收来自苹果服务器给你返回的deviceToken,然后你需要将它添加到你本地的推送服务器上。(很重要,决定你的设备能不能接收到推送消息)。

 

– (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{

NSLog(@”userInfo == %@”,userInfo);
NSString *message = [[userInfo objectForKey:@”aps”]objectForKey:@”alert”];

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@”提示” message:message delegate:self cancelButtonTitle:@”取消” otherButtonTitles:@”确定”, nil];

[alert show];
}
这个函数则是当设备接收到来自苹果推送服务器的消息时触发的,用来显示推送消息。

 

 

– (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error{

NSLog(@”Regist fail%@”,error);
}
当注册失败时,触发此函数。

 

 

2.PHP服务端

将simplepush.php这个推送脚本也放在push文件夹中

 

<?php

// ??????????deviceToken???????????????
$deviceToken = ‘c95f661371b085e2517b4c12cc76293522775e5fd9bb1dea17dd80fe85583b41’;

// Put your private key’s passphrase here:
$passphrase = ‘abc123’;

// Put your alert message here:
$message = ‘My first push test!’;

$ctx = stream_context_create();
stream_context_set_option($ctx, ‘ssl’, ‘local_cert’, ‘ck.pem’);
stream_context_set_option($ctx, ‘ssl’, ‘passphrase’, $passphrase);

// Open a connection to the APNS server
//??????????
//$fp = stream_socket_client(?ssl://gateway.push.apple.com:2195?, $err, $errstr, 60, //STREAM_CLIENT_CONNECT, $ctx);
//?????????????appstore??????
$fp = stream_socket_client(
‘ssl://gateway.sandbox.push.apple.com:2195’, $err,
$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);

if (!$fp)
exit(“Failed to connect: $err $errstr” . PHP_EOL);

echo ‘Connected to APNS’ . PHP_EOL;

// Create the payload body
$body[‘aps’] = array(
‘alert’ => $message,
‘sound’ => ‘default’
);

// Encode the payload as JSON
$payload = json_encode($body);

// Build the binary notification
$msg = chr(0) . pack(‘n’, 32) . pack(‘H*’, $deviceToken) . pack(‘n’, strlen($payload)) . $payload;

// Send it to the server
$result = fwrite($fp, $msg, strlen($msg));

if (!$result)
echo ‘Message not delivered’ . PHP_EOL;
else
echo ‘Message successfully delivered’ . PHP_EOL;

// Close the connection to the server
fclose($fp);
?>
deviceToken填写你接收到的token,passPhrase则填写你的ck.pem设置的密码。

此刻就是见证奇迹的时候了,使用终端进入到push文件夹,在终端输入 php simplepush.php

 

若显示以上提示则表示推送成功了。附上一张成功图。

%title插图%num

推送就到这边吧!

各路神仙, ios14 还有办法使用 callKit 吗

每次来电语音或者视频都不弹框,只震动,强迫症有点受不了,在知乎看说用 HK 的手机号注册就可以用,有大佬试过吗
第 1 条附言  ·  17 小时 53 分钟前

使用易博通开了个 HK 的号码,现在新注册的可以使用 callkit 了,这个卡 18 一个月,费用有点高,打算后期切换 GV,不知道切换之后还能不能用。
70 条回复    2021-03-30 07:58:14 +08:00
opooooos
    1

opooooos   23 小时 23 分钟前

办法一:越狱
办法二:实体境外卡重新注册新微信,GV 等一众虚拟号全都不行。
opooooos
    2

opooooos   23 小时 22 分钟前

有可能还有办法三,你去起诉工信部。
mschultz
    3

mschultz   23 小时 11 分钟前   ❤️ 1

用香港手机号重新注册微信吧。

迁移好友是世界级难题,但是一旦体验了微信迁移好友的难度,希望以后在 [非必要] 使用微信的时候,你可以心理上多多排斥一下微信?

Heyzg
    4

Heyzg   23 小时 5 分钟前 via iPhone

买了一个微信多开,tf 上架,设置里有开启 callkit 但是微信版本目前只能是 7.0.12
a302800411
    5

a302800411   22 小时 46 分钟前   ❤️ 2

换个思路,你*经常语音的对象是谁,能不能和 TA 约定换一个 IM App
NeezerGu
    6

NeezerGu   22 小时 18 分钟前

@opooooos
好像是说,先拿境外的实体卡,随便一个,注册。之后再切换到 gv 就行
NeezerGu
    7

NeezerGu   22 小时 18 分钟前   ❤️ 1

@mschultz 只需要假装自己被封号了,就会发现迁移好友很简单
mschultz
    8

mschultz   22 小时 11 分钟前   ❤️ 1

@NeezerGu 我旧号 400 多好友,香港新号用了 3 年了,陆陆续续加了 100 多好友。

现在偶尔切回旧号看一眼,发现半年内旧号都没有一条私聊消息。旧号比新号多的 300 多好友都懒得加了?

outtime
    9

outtime   22 小时 11 分钟前

我的方案,淘宝买个卡注册 wx,然后绑 gv 。
正常使用,总花费 30 左右,迁移好友的话基本是遇到一个迁一个,慢慢来,主要是微信绑定的东西好麻烦,但是 callkit 真的很舒服
VersusClyne
    10

VersusClyne   22 小时 9 分钟前   ❤️ 1

境外号注册 用了三年了
微信支付支持多货币可切换
新消息通知支持系统电话接听( callkit )
特殊时期可以随意改名换头像
用户数据存放在香港 /加拿大 隐私策略按照欧盟标准走
有 WeChat Out (国内号换绑国外号也有)
可以搜索并且关注港澳台微信公众号
无关键词过滤

缺点
有些微信表情包无法直接在表情中心搜到(版权 /授权地区限制)
但是好友发给你其中的表情的话是可以添加的(可能失效)

一直用的 iOS 所以不知道 Android Play Store 版的区别

lisr
    11

lisr   22 小时 6 分钟前

@VersusClyne 我注册之后关注 WSJ 公众号一直提示注册未完成,想问一下有没有出现过这种情况
yitingbai
    12

yitingbai   22 小时 6 分钟前

@mschultz 既然都没有人联系你, 没有 Callkit 也没关系吧
fox1751
    13

fox1751   22 小时 5 分钟前 via iPhone

生活的方方面面都会有受到中国特色的影响,逼得人不得不关注
VersusClyne
    14

VersusClyne   22 小时 3 分钟前

@lisr 没有 我关注使用一切正常
pierrec
    15

pierrec   21 小时 34 分钟前

迁移遇到过的痛点,再也用不了麦当劳小程序(微信绑定了国内手机号
OYSTER2DO
    16

OYSTER2DO   21 小时 23 分钟前

@lisr 那是 WSJ 的公众号注册未完成,和你没关系
cjban
    17

cjban   21 小时 13 分钟前

@Heyzg 能分享一下购买链接吗? tg:@ghyuqnad
opooooos
    18

opooooos   21 小时 0 分钟前

@NeezerGu 可行
目前自己在用,国内银行卡也可以。
但前提还是要有个国外实体卡
ygchy
    19

ygchy   20 小时 54 分钟前

@outtime 同求关键词
RobertLyu
    20

RobertLyu   20 小时 24 分钟前 via iPhone

@lisr 我这边也是这样提示的。所以已经开了 WSJ 订阅。后来发现 Apple News+ 有原版杂志。
Removable
    21

Removable   20 小时 20 分钟前

@Heyzg 同求一个,谢谢~!
JaminTeng
    22

JaminTeng   20 小时 14 分钟前 via iPhone

我用法国号注册了秒封
RobertLyu
    23

RobertLyu   20 小时 6 分钟前 via iPhone

@JaminTeng 不要主动加人,要验证之后过几天,然后让对方扫二维码加好友。前一周不要超过 10 个人。

而且如果加人太多会出现自己看不到但对方能看到的诈骗风险提醒。得慢慢养号。微信破事可多了。

HashV2
    24

HashV2   20 小时 0 分钟前

@opooooos 越狱可以不换号 callkit 么? 怎么操作的?
zhuangku556
    25

zhuangku556   19 小时 39 分钟前

无所谓,你不如微信语音视频都不接,对面自然会打你电话
AshFive
    26

AshFive   19 小时 37 分钟前

imazing 配合抓包 ,微信 6.6.1
前提是如果你能忍受老版本的残缺功能。
mschultz
    27

mschultz   19 小时 30 分钟前 via iPhone

@yitingbai 是的,之前还有几个人会给我打微信,现在基本工作生活都不依赖微信了哈哈
Heyzg
    28

Heyzg   19 小时 28 分钟前 via iPhone

@cjban
@Removable

http://wyj.5ta.co/index/index/good?good_id=222001&page=&good_type=

我什么都不保证,这个网址当初也是我在百度搜的,我只是自己买了一个叫微小白的目前在用

Eric08214
    29

Eric08214   19 小时 25 分钟前 via iPhone

@outtime 我*近也在看,你是选了什么卡?可以推荐一下吗
eastlhu
    30

eastlhu   19 小时 16 分钟前

@Heyzg 这种 hook 的随便获取你账号密码。。注意安全
Aary
    31

Aary   19 小时 6 分钟前

记住了,以后别买国行。
sobigfish
    32

sobigfish   19 小时 4 分钟前

@mschultz 请问 hk 号可以绑定国内银行卡支付吗?
outtime
    33

outtime   19 小时 2 分钟前

@Eric08214
@ygchy 大概就是搜国家+卡之类的,然后问卖家能不能注册 vx 。
gv 的话我是在 tg 上面跟着买的。
supahotfire
    34

supahotfire   18 小时 29 分钟前 via iPhone   ❤️ 1

这也差不多是我跟我老婆都买美版机的原因,为了用 facetime audio 通话
653513754
    35

653513754   18 小时 11 分钟前

@mschultz 好像保存离线是有时间限制的,太久的离线消息就不推给你了
avia1ae
    36

avia1ae   18 小时 10 分钟前

越狱,然后用插件,不开那些容易被检测的功能,这样算是代价比较小的办法
christin
    37

christin   18 小时 5 分钟前 via iPhone

国内手机号注册,正常挂机几天后改绑 gv 号。目前没有绑定银行卡,callkit 正常使用
OYSTER2DO
    38

OYSTER2DO   18 小时 0 分钟前

@christin 国内号注册不可能用 Callkit,沾上就废了
christin
    39

christin   17 小时 58 分钟前 via iPhone

@OYSTER2DO 我就是这么操作的 现在可以用
xz
    40

xz   17 小时 53 分钟前

感谢楼上的神仙们,刚让朋友用国外的手机号帮忙注册了个微信 试了下是有 callkit 的 准备移到 gv 上
opooooos
    41

opooooos   17 小时 38 分钟前

@HashV2 可以不换号
不过我好久没越狱了。
我记得上个版本的越狱还有人发了包含 callkit 的源。google 一下吧。
Leonard
    42

Leonard   17 小时 36 分钟前

@Aary #31 这个和手机国行与否没有关系,就算外版手机,只要微信用了(用过)+86 一样没有 callkit
coobin
    43

coobin   16 小时 57 分钟前

楼主能给一个易博通的推荐码么,看上去能增加 15 天时间。
FS1P7dJz
    44

FS1P7dJz   16 小时 54 分钟前

因为都不给微信麦克分和摄像头权限
所以 callkit 正好不需要了
手动狗头
yitingbai
    45

yitingbai   16 小时 47 分钟前

@supahotfire 原来如此啊, 我还在奇怪为什么都在说不能用 facetime audio, 我却经常用, 原来我是美版的
salor
    46

salor   16 小时 43 分钟前 via iPhone

艹,想起还有一个 hk 号快过期还没启用。找出来塞进许久没用的备用机,漫游流量瞬间给我干欠费了。

mschultz
    47

mschultz   16 小时 40 分钟前

@653513754 你的意思是 literally 会丢消息?

比如我有 A 、B 两个微信号,目前 B 号登录状态;
有人给我 A 号发了条消息,我半年后才从「设置 – 切换帐号」功能临时登上 A 号查看,但那条消息已经从微信的系统里永久丢掉了?我的 A 号永远也收不到那条消息了?我没研究过,直觉上不应该是这样吧?

gawoo
    48

gawoo   16 小时 40 分钟前

@opooooos 还有一个用微信旧版本
mschultz
    49

mschultz   16 小时 24 分钟前   ❤️ 1

@sobigfish #32 可以,你可以同时拥有香港和中国大陆两个微信钱包,有一个菜单可以切换。注册时是香港号,后面绑定大陆银行卡开通大陆版微信支付,不影响 CallKit 。

另外一个微信可以「存储」多个手机号。就是你用微信快捷登录其他应用(如小程序)的时候,微信会提示“XXX 申请使用 你的手机号码”,然后这个手机号码列表可以有不止一个手机号。这里你可以添加自己的内地手机号,以后有什么中国大陆 App 要用微信登录的话,可以直接授权内地手机号,同时保持微信本身绑定的是境外号码不变。

但是千万别把「帐号与安全 – 手机号」里面那个手机号手贱改成 +86 号码。

Removable
    50

Removable   16 小时 23 分钟前

@supahotfire 老哥美版都在哪买的啊?
yeeyeung
    51

yeeyeung   16 小时 20 分钟前

@Removable 港版豆芽,美版淘宝
Removable
    52

Removable   15 小时 47 分钟前

@yeeyeung #50 我搜到了个“苏宁豆芽”,是这个吗?
hackpro
    53

hackpro   15 小时 46 分钟前 via iPad

下载 7.0.21 版本不是可以吗?
hackpro
    54

hackpro   15 小时 43 分钟前 via iPad

错了 *后一个版本应该是 6.6.1
aptupdate
    55

aptupdate   15 小时 41 分钟前 via iPhone

@mschultz 这个好,而且买个 hk 卡也不麻烦,就是转移好友有点难受。
coleridgeguo
    56

coleridgeguo   15 小时 37 分钟前 via iPhone

想问一下 我是美版 iPhone 人在国外有国外 sim 卡 我的微信号原来是国内手机号注册的我刚改成了美国的手机号 但是还是没有 callkit 显示怎么办?
Citronl
    57

Citronl   15 小时 8 分钟前

@coleridgeguo #56 必须国外手机号注册才有,换绑没用。
mschultz
    58

mschultz   15 小时 5 分钟前   ❤️ 2

@coleridgeguo 不越狱不换手机不换微信号的暂时无解。楼上有人说了,千万别沾 +86 号,不管是*初注册时还是之后修改的。

微信号转入中国可以,老乡来了就别想走了?

leekayui
    59

leekayui   13 小时 43 分钟前

@mschultz #58 哈哈哈 实践证明,确实如此
Fri
    60

Fri   13 小时 8 分钟前

中国移动的无忧行 APP 可以买香港的虚拟 SIM 卡,实测可以注册微信。
supahotfire
    61

supahotfire   12 小时 50 分钟前

@yitingbai 自己的是家里人带回来的,老婆的是海淘的
supahotfire
    62

supahotfire   12 小时 49 分钟前

@yitingbai 对啊,不过双方都得是美版或者其他没有阉割 audio 的版本才行
orangy
    63

orangy   12 小时 44 分钟前 via iPhone

@mschultz 香港手机号注册之后还是有很多地方需要绑定内地手机号,比如钱包功能,那个应该没关系吧?
ccming
    64

ccming   12 小时 40 分钟前 via iPhone

@NeezerGu 不用假装,发点什么就能成真
he110comex
    65

he110comex   12 小时 3 分钟前 via Android

安卓倒是很好解决,Google play 下载的微信就行。一直在用,就是想让通知及时送达,必须长开梯子。因为通知走 Google 通道。
ygchy
    66

ygchy   11 小时 43 分钟前

和楼主一样试着开了个易博通,注册时候被要求其他用户扫码验证……这是不算翻车了?
yeeyeung
    67

yeeyeung   8 小时 36 分钟前

@Removable weibo”豆芽手机店”
hanksun
    68

hanksun   7 小时 28 分钟前 via iPhone

刚用英国实体卡注册微信,需要朋友扫码连坐?
hkezh
    69

hkezh   2 小时 5 分钟前 via iPhone

我注册了香港的,用的易博通,需要好友验证。刚刚用叮咚注册了欧盟的。欧盟缺点就是表情商店打不开。其它的都正常。打算把 700 好友迁入到其中一个
Jasmine2016
    70

Jasmine2016   1 小时 55 分钟前

楼上已经有人提到了换用旧版微信,这可能是目前对你来说*不折腾的办法。唯一一点你可能不能适应的,就是微信 6.6.7 之前的版本并没有适配异形屏,所以如果你用 iPhone11 或者 12 这样的机器,会发现分辨率低一个层级。如果你是旧手机( iPhone 8 或之前),那丝毫不影响你的使用体验。

无法访问此网站 localhost 拒*了我们的连接请求。

开机之后打开IDEA,运行了一个与MySQL、Tomcat连接着的项目,弹出的界面却无法打开,呈现的是下图这种样子,且发现地址栏不正确

%title插图%num
查看MySQL,发现MySQL运转正常

%title插图%num
打开IDEA,发现以往是Tomcat的地方变成了这样

%title插图%num

再把它改成tomcat,如图

然后,点击运行,运行成功,如下图,内容我马赛克掉了~

%title插图%num
总结一下,出现这种情况应该是在IDEA界面点击运行时,当前页面并不是应该展示的首页,服务器选择错了,才会跳出错误的界面。具体原因还没有深究,欢迎大家来讨论呀

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速