iOS零碎知识点<初级版>
iOS零碎知识点<中阶版>
iOS零碎知识点<高阶版>
iOS零碎知识点<工具篇>
优雅的隐藏tabbar
很多APP都使用TabBarController套NavigationController的方法来作为应用的框架,那么隐藏TabBar就成了一个必要的功能,目前最简单的方法还是使用hidesBottomBarWhenPushed来实现,最简单的方法就是在要隐藏tab bar的Controller里写入下面的方法(可以写个类扩展来更方便)来覆默认值:
- (BOOL)hidesBottomBarWhenPushed {
return (self.navigationController.topViewController == self);
}
把UITableview在editing模式下的drag按钮去掉,换成自己的样式,并保留原生拖动排序的行为
先找到了UITableViewCell的结构,并将拖动按钮替换:
//打印出来的自定义的cell在editing模式下的结构
(lldb) po self
>
(lldb) po self.subviews
5 elements
- [0] : ; layer = >
- [1] : <_UITableViewCellSeparatorView: 0x7d087c40; frame = (15 55; 305 1); layer = >
- [2] : <_UITableViewCellSeparatorView: 0x7b163240; frame = (15 55.5; 305 0.5); layer = >
- [3] : >
- [4] : >
(lldb) po self.subviews.last
Optional
- Some : >
(lldb) po self.subviews.last?.subviews
Optional>
Some : 1 elements
- [0] : >
(lldb)
可以看到此时contentView左右都向内缩进了一定的距离,最后有一个view叫UITableViewCellReorderControl,就是它了,然后看它的subviews,竟然包含了一个UIImageView,果断替换之,代码如下:
override func layoutSubviews() {
super.layoutSubviews()
setupReorderControl()
}
func setupReorderControl() {
if (self.reorderControl != nil) {
return;
}
for view in self.subviews {
if view.description.containsString("UITableViewCellReorderControl") {
self.reorderControl = view
}
}
if ((self.reorderControl) != nil)
{
let imageOfReorder = self.reorderControl?.subviews[0] as? UIImageView
imageOfReorder?.removeFromSuperview()
}
}
此时就完成了将拖动按钮隐藏的功能,但是注意,在这里如果想通过设置reorderControl的frame去改变它的位置是不成功的,我想可能它的布局使用autolayout,并没有深入的再去研究。
改變導航欄返回按鈕,iOS11上失效:
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonImage
forState:UIControlStateNormal
barMetrics:UIBarMetricsDefault];
//自定义文字部分
[[UIBarButtonItem appearance] setBackButtonTitlePositionAdjustment:UIOffsetMake(NSIntegerMin, NSIntegerMin) forBarMetrics:UIBarMetricsDefault];
判定滚动手势是往上还是往下拖拽
//scrollView已经有拖拽手势,直接拿到scrollView的拖拽手势
UIPanGestureRecognizer *pan = scrollView.panGestureRecognizer;
CGFloat velocity = [pan velocityInView:scrollView].y;
CGFloat offsetY = _lastPointY - scrollView.contentOffset.y;
if (velocity < -5) {
//向上拖动,隐藏导航栏
} else if (velocity > 5) {
//向下拖动,显示导航栏
} else if(velocity == 0){
//停止拖拽
}
ios UITableview header不随着滚动
//去掉UItableview headerview黏性(sticky)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat sectionHeaderHeight = 40;
if (scrollView.contentOffset.y<=sectionHeaderHeight&&scrollView.contentOffset.y>=0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
} else if (scrollView.contentOffset.y>=sectionHeaderHeight) {
scrollView.contentInset = UIEdgeInsetsMake(-sectionHeaderHeight, 0, 0, 0);
}
}
UITableView的子视图会自动往上偏移64
大概结构是这样的:tableview上添加了个subview,然后设置了tableview的内偏移为subview的高,还设置了tableview的tableFooterView;运行后发现tableview的contentoffset自动往上多偏移了64;
self.automaticallyAdjustsScrollViewInsets = NO;
centerTableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenHeight - kNavgationHeight) style:UITableViewStyleGrouped];
centerTableView.showsVerticalScrollIndicator = NO;
centerTableView.tableFooterView = self.bottomView;
[centerTableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
centerTableView.contentInset = UIEdgeInsetsMake(_centerViewHeight, 0, 0, 0);
[self.view addSubview:centerTableView];
_userHeadView = [BKUserCenterView loadViewFromNib];
_userHeadView.frame = CGRectMake(0, -_centerViewHeight, kScreenWidth, _centerViewHeight);
[centerTableView addSubview:_userHeadView];
导致原因:先设置了tableFooterView,再addSubview: 导致tableview的计算就会不准确,把设置tableFooterView 放到 addSubview: 后面就好了,更改后如下:
.......上面其它代码不变......
[centerTableView addSubview:_userHeadView];
centerTableView.tableFooterView = self.bottomView;
获取相片的位置等信息:
NSURL *assetURL = [info objectForKey:UIImagePickerControllerReferenceURL];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
__block NSMutableDictionary *imageMetadata_GPS = nil;
__weak typeof(self)weakSelf = self;
[library assetForURL:assetURL resultBlock:^(ALAsset *asset) {
//获取时间
NSDate* pictureDate = [asset valueForProperty:ALAssetPropertyDate];
NSDateFormatter * formatter = [[NSDateFormatter alloc]init];
formatter.dateFormat = @"yyyy:MM:dd HH:mm:ss";
formatter.timeZone = [NSTimeZone localTimeZone];
NSString * pictureTime = [formatter stringFromDate:pictureDate];
weakSelf.time.text = pictureTime;
//获取GPS
imageMetadata_GPS = [[NSMutableDictionary alloc] initWithDictionary:asset.defaultRepresentation.metadata];
NSDictionary *GPSDict=[imageMetadata_GPS objectForKey:(NSString*)kCGImagePropertyGPSDictionary];
if (GPSDict!=nil) {
CLLocation *loc=[GPSDict locationFromGPSDictionary];
weakSelf.weidu.text = [NSString stringWithFormat:@"%f", loc.coordinate.latitude];
weakSelf.jingdu.text = [NSString stringWithFormat:@"%f", loc.coordinate.longitude];
CLGeocoder *clGeoCoder = [[CLGeocoder alloc] init];
CLLocation *newLocation = [[CLLocation alloc] initWithLatitude:loc.coordinate.latitude longitude:loc.coordinate.longitude];
//反向地理编码的请求 -> 根据经纬度 获取 位置
[clGeoCoder reverseGeocodeLocation:newLocation completionHandler: ^(NSArray *placemarks,NSError *error) {
for (CLPlacemark *placeMark in placemarks)
{
NSDictionary *addressDic=placeMark.addressDictionary;
NSArray *location_Arr = [addressDic objectForKey:@"FormattedAddressLines"];//系统格式化后的位置
weakSelf.location.text = [location_Arr firstObject];
}
}];
}else{
//@"此照片没有GPS信息";
//@"此照片没有GPS信息";
//@"此照片没有拍摄位置";
}
}
failureBlock:^(NSError *error) {
}];
}
获取网络图片的宽高
+(CGSize)getImageSizeWithURL:(id)imageURL
{
NSURL * url = nil;
if ([imageURL isKindOfClass:[NSURL class]]) {
url = imageURL;
}
if ([imageURL isKindOfClass:[NSString class]]) {
url = [NSURL URLWithString:imageURL];
}
if (!url) {
return CGSizeZero;
}
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
CGFloat width = 0, height = 0;
if (imageSourceRef) {
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSourceRef, 0, NULL);
if (imageProperties != NULL) {
CFNumberRef widthNumberRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelWidth);
if (widthNumberRef != NULL) {
CFNumberGetValue(widthNumberRef, kCFNumberFloat64Type, &width);
}
CFNumberRef heightNumberRef = CFDictionaryGetValue(imageProperties, kCGImagePropertyPixelHeight);
if (heightNumberRef != NULL) {
CFNumberGetValue(heightNumberRef, kCFNumberFloat64Type, &height);
}
CFRelease(imageProperties);
}
CFRelease(imageSourceRef);
}
return CGSizeMake(width, height);
}
tableView下拉导航栏上头像变大:
UIView *titleView = [[UIView alloc] init];
self.navigationItem.titleView = titleView;
self.headerImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"head.jpg"]];
self.headerImageView.layer.cornerRadius = 35;
self.headerImageView.layer.masksToBounds = YES;
self.headerImageView.frame = CGRectMake(0, 0, 70, 70);
self.headerImageView.center = CGPointMake(titleView.center.x, 0);
[titleView addSubview:self.headerImageView];
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat offsetY = scrollView.contentOffset.y + scrollView.contentInset.top;
CGFloat scale = 1.0;
// 放大
if (offsetY < 0) {
// 允许下拉放大的最大距离为300
// 1.5是放大的最大倍数,当达到最大时,大小为:1.5 * 70 = 105
// 这个值可以自由调整
scale = MIN(1.5, 1 - offsetY / 300);
} else if (offsetY > 0) { // 缩小
// 允许向上超过导航条缩小的最大距离为300
// 为了防止缩小过度,给一个最小值为0.45,其中0.45 = 31.5 / 70.0,表示
// 头像最小是31.5像素
scale = MAX(0.45, 1 - offsetY / 300);
}
self.headerImageView.transform = CGAffineTransformMakeScale(scale, scale);
// 保证缩放后y坐标不变
CGRect frame = self.headerImageView.frame;
frame.origin.y = -self.headerImageView.layer.cornerRadius / 2;
self.headerImageView.frame = frame;
}
画一个渐变色的圆
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddArc(ctx, self.centerX, self.centerY, self.height / 2, 0, M_PI * 2, 0);
CGContextSetLineWidth(ctx, 8);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// 颜色数组
NSArray *colors = @[
(id)[UIColor colorWithRed:86 / 255.f green:216 / 255.f blue:252 / 255.f alpha:1].CGColor,
(id)[UIColor colorWithRed:248 / 255.f green:114 / 255.f blue:155 / 255.f alpha:1].CGColor
];
CGGradientRef gradientRef = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colors, NULL);
CGColorSpaceRelease(colorSpace);
CGContextReplacePathWithStrokedPath(ctx);
CGContextClip(ctx);
CGPoint beginPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(rect.size.width, rect.size.height);
CGContextDrawLinearGradient(ctx, gradientRef, beginPoint, endPoint, 0);
CGGradientRelease(gradientRef);
CGContextStrokePath(ctx);
}
去除掉首尾的空白字符和换行字符
[address stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//替换\n
[address stringByReplacingOccurrencesOfString:@"\t" withString:@""];
ios打包ipa的四种实用方法(.app转.ipa)
基本数据类型的取值范围:
unsigned int 0~4294967295
int -2147483648~2147483647
unsigned long 0~4294967295
long -2147483648~2147483647
long long的最大值:9223372036854775807
long long的最小值:-9223372036854775808
unsigned long long的最大值:1844674407370955161
__int64的最大值:9223372036854775807
__int64的最小值:-9223372036854775808
unsigned __int64的最大值:18446744073709551615
修改 UIAlertController、UIAlertAction文字颜色
UIAlertController * alertContro = [[UIAlertController alloc] init];
NSMutableAttributedString *hogan = [[NSMutableAttributedString alloc] initWithString:@"heihei"];
[hogan addAttribute:NSFontAttributeName value:kMediumFontIsB4IOS9(16) range:NSMakeRange(0, [[hogan string] length])];
[hogan addAttribute:NSForegroundColorAttributeName value:[UIColor redColor] range:NSMakeRange(0, [[hogan string] length])];
[alertContro setValue:hogan forKey:@"attributedTitle"];
// UIAlertAction
alertAction setValue:kRedColor forKey:@"_titleTextColor"];
Mac OS 10.12.3如何添加永久静态路由(亲测无效,不知道是我那里执行错了!)
https://discussionschinese.apple.com/thread/102393?start=0&tstart=0
判定是否有导入了某个头件
#if __has_include(<xxxxx/xxxxx.h>)
#import <xxxxx/xxxxx.h>
#else
#import "zzzzz.h"
#endif
动态库与静态库
区別:
静态库:链接时会被完整的复制到可执行文件中,被多次使用就有多份拷贝。
动态库:链接时不复制,程序运行时由系统动态加载到内存,系统只加载一次,多个程序共用(如系统的UIKit.framework等),节省内存。
-
如果静态库中有category类,则在使用静态库的项目配置中Other Linker Flags需要添加参数-ObjC或者-all_load。
如果创建的framework类中使用了.tbd,则需要在实际项目中导入.tbd动态库。 -
Mach-O格式,因为动态库也可以是以framework形式存在,所以需要设置,否则默认打出来的是动态库。将target->BuildSetting->Mach-o Type 设为Static Library(默认为Dynamic Library)
-
架構:
模拟器:
iPhone4s-iPnone5:i386
iPhone5s-iPhone7 Plus:x86_64
真机:
iPhone3gs-iPhone4s:armv7
iPhone5-iPhone5c:armv7s
iPhone5s-iPhone7 Plus:arm64
支持armv7的静态库可以在armv7s上正常运行。
手机号判定:
- 方法一:建议使用这个
if (self.length != 11)
{
return NO;
}
/**
* 手机号码:
* 13[0-9], 14[5,7], 15[0, 1, 2, 3, 5, 6, 7, 8, 9], 17[0, 1, 6, 7, 8], 18[0-9]
* 移动号段: 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188
* 联通号段: 130,131,132,145,155,156,170,171,175,176,185,186
* 电信号段: 133,149,153,170,173,177,180,181,189
*/
NSString *MOBILE = @"^1(3[0-9]|4[57]|5[0-35-9]|7[0135678]|8[0-9])\\d{8}$";
/**
* 中国移动:China Mobile
* 134,135,136,137,138,139,147,150,151,152,157,158,159,170,178,182,183,184,187,188
*/
NSString *CM = @"^1(3[4-9]|4[7]|5[0-27-9]|7[08]|8[2-478])\\d{8}$";
/**
* 中国联通:China Unicom
* 130,131,132,145,155,156,170,171,175,176,185,186
*/
NSString *CU = @"^1(3[0-2]|4[5]|5[56]|7[0156]|8[56])\\d{8}$";
/**
* 中国电信:China Telecom
* 133,149,153,170,173,177,180,181,189
*/
NSString *CT = @"^1(3[3]|4[9]|53|7[037]|8[019])\\d{8}$";
NSPredicate *regextestmobile = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", MOBILE];
NSPredicate *regextestcm = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CM];
NSPredicate *regextestcu = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CU];
NSPredicate *regextestct = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", CT];
if (([regextestmobile evaluateWithObject:self] == YES)
|| ([regextestcm evaluateWithObject:self] == YES)
|| ([regextestct evaluateWithObject:self] == YES)
|| ([regextestcu evaluateWithObject:self] == YES))
{
return YES;
}
else
{
return NO;
}
}
- 方法二:
- (BOOL)isMobile{
NSString *regexStr = @"^1[3,8]\\d{9}|14[5,7,9]\\d{8}|15[^4]\\d{8}|17[^2,4,9]\\d{8}$";
NSError *error;
NSRegularExpression *regular = [NSRegularExpression regularExpressionWithPattern:regexStr options:NSRegularExpressionCaseInsensitive error:&error];
if (error) return NO;
NSInteger count = [regular numberOfMatchesInString:self options:NSMatchingReportCompletion range:NSMakeRange(0, self.length)];
if (count > 0) {
return YES;
} else {
return NO;
}
}
适配ios11 @available属性oc工程无法在xcode9以下工程编译问题解决方案:
# ifdef __IPHONE_11_0
if (@available(iOS 11.0, *)) {
self.leftContentView.contenView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} else {
self.automaticallyAdjustsScrollViewInsets = NO;
}
#else
self.automaticallyAdjustsScrollViewInsets = NO;
#endif
获取WKWebview的内容高度
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
[webView evaluateJavaScript:@"document.body.offsetHeight" completionHandler:^(id _Nullable result, NSError * _Nullable error) {
CGFloat documentHeight = [result doubleValue];
wkWebViewHeight = documentHeight;
CGRect webFrame = webView.frame;
webFrame.size.height = wkWebViewHeight;
webView.frame = webFrame;
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObjects:[NSIndexPath indexPathForRow:3 inSection:0], nil] withRowAnimation:UITableViewRowAnimationNone];
}];
}
判定是否可以导入某个头文件:
//判定是否可以导入某头文件
#if __has_include(<xxxxxxx/xxxxxx.h>)
//do sth
#import <xxxxxxx/aaaaaa.h>
//判定是否可以导入某framework库的头文件
#elif __has_include(<YYWebImage/YYImage.h>)
//do sth
#else
//do sth
#endif
查看.a库是否支持bitcode:
1. 首先需要判断 library 是否是 fat 的,可以用 lipo 命令:
lipo -info ibDHxls.a
2. 如果是 fat library(支持多构架),需要将某个 CPU 架构的 slice 提取出来:
lipo -thin arm64 ibDHxls.a -output libd-arm64.a
3. 接下来我们需要将这个 slice 里面的目标文件解压出来,可以用 ar 命令:
ar -x libd-arm64.a
4. 解压完后当前的目录下会有多个.o文件,你选择其中一个.o文件执行下面的命令就可以:
otool -l unit.o | grep bitcode //我这里选择的是:`unit.o`
5. 如果找到了,说明第三方库是支持 bitcode 的:
`sectname __bitcode`
禁止WKWebView缩放:
- 在HTML里边的mata标签加入user-scalable = no,若是原生js交互的话可采用注入js的方法更改meta。
- (void)webView:(WKWebView*)webView didFinishNavigation:(WKNavigation*)navigation
{
NSString *injectionJSString = @"var script = document.createElement('meta');"
"script.name = 'viewport';"
"script.content=\"width=device-width, initial-scale=1.0,maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\";"
"document.getElementsByTagName('head')[0].appendChild(script);";
[webView evaluateJavaScript:injectionJSString completionHandler:nil];
}
- 第一种方法的缺点:你无法再手动控制缩放比例了,并且在双击、或者遇到文本输入的时候,可能还是会自动缩放,下面使用第二种方法完美控制(ios11之前,ios之后失效):
- 设置代理
self.wk_WebView.scrollView.delegate = self;
- webview的控制类中,设立一个控制属性,并初始化设置为YES:
self.allowZoom = YES;
- 实现scrollView的代理:
// wkwebview在加载网页之后,会先自动适应缩放一次,
// 如果在这个代理方法中直接return nil,会导致无法按系统建议的缩放比例正确的显示,
// 所以需要利用我们申明的那个bool值来控制
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
//让它只有在我们允许的时候,才能缩放
if(self.allowZoom){
return nil;
}else{
return self.wk_WebView.scrollView.subviews.firstObject;
}
}
- 在网页加载完之后,(此时系统已经为我们缩放了网页),关闭缩放
-(void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
self.allowZoom = NO;
}
- 需要时,手动控制:
self.allowZoom = YES;
[self.wk_WebView.scrollView setZoomScale:1.2 animated:NO];
手机基本信息
iOS 将时间NSDate转化为毫秒时间戳
对于将NSDate类型转换为时间戳,有直接转10位数值的时间戳方法,但是没有精确到毫秒,这种数值在转化为 NSDate类型的时候,就会出点儿错,每一个时间的毫秒都是为000的;
因为 [[NSDate date] timeIntervalSince1970] 虽然可以获取到后面的毫秒、微秒 ,但是在保存的时候省略掉了。如一个时间戳不省略的情况下为 1395399556.862046 ,省略掉后为一般所见 1395399556 。所以想取得毫秒时用获取到的时间戳 *1000 ,想取得微秒时 用取到的时间戳 * 1000 * 1000 。这样就解释了上面的10位数值的问题,当你取毫秒的时候,就会变成13位数值了。我想这样大家应该明白了吧!
2个小函数,这2个函数呢,是互逆的:
- 将时间戳转换为NSDate类型
-(NSDate *)getDateTimeFromMilliSeconds:(long long) miliSeconds
{
NSTimeInterval tempMilli = miliSeconds;
NSTimeInterval seconds = tempMilli/1000.0;//这里的.0一定要加上,不然除下来的数据会被截断导致时间不一致
NSLog(@"传入的时间戳=%f",seconds);
return [NSDate dateWithTimeIntervalSince1970:seconds];
}
- 将NSDate类型的时间转换为时间戳,从1970/1/1开始
-(long long)getDateTimeTOMilliSeconds:(NSDate *)datetime
{
NSTimeInterval interval = [datetime timeIntervalSince1970];
NSLog(@"转换的时间戳=%f",interval);
long long totalMilliseconds = interval*1000 ;
NSLog(@"totalMilliseconds=%llu",totalMilliseconds);
return totalMilliseconds;
}
网友评论