美文网首页
iOS开发 小技巧 + 踩坑记

iOS开发 小技巧 + 踩坑记

作者: 东也_ | 来源:发表于2020-06-08 17:59 被阅读0次

    持续更新,不断积累... 欢迎留言!

    1、status bar电池栏 强制刷新

    在控制器中调用

     if([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]){
                       [self prefersStatusBarHidden];
                       [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
                   }
    

    2、监听设备旋转方向并且旋转视图

    //添加通知
     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
    //开始监听
    - (void)deviceOrientationDidChange
    {
            if([UIDevice currentDevice].orientation == UIDeviceOrientationPortrait) {
                self.isRightLandscape = false;
                [self orientationChange:NO];
             } else if ([UIDevice currentDevice].orientation == UIDeviceOrientationLandscapeLeft) {
                self.isRightLandscape = true;
                [self orientationChange:YES];
               
            }
    }
    
    - (void)orientationChange:(BOOL)landscapeRight
    {
        //隐藏status bar
        if([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]){
                       [self prefersStatusBarHidden];
                       [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
                   }
        CGFloat width = self.view.frame.size.width;
        CGFloat height = self.view.frame.size.height;
        if (landscapeRight) {
            [UIView animateWithDuration:0.2f animations:^{
                self.view.transform = CGAffineTransformMakeRotation(M_PI_2);
                self.view.bounds = CGRectMake(0, 0, height, width);
            }];
        } else {
            [UIView animateWithDuration:0.2f animations:^{
                self.view.transform = CGAffineTransformMakeRotation(0);
                self.view.bounds = CGRectMake(0, 0, width, height);
            }];
        }
    }
    
    

    3、折叠函数方法快捷键

    command + option + left or + right = 折叠 or 展开

    4、不管项目配置如何,让某个控制支持任意方向旋转。

    屏幕旋转的方式大概有以下几种:
    1、旋转view
    2、旋转的时候跳转一个只支持横屏的控制器
    3、重写appdelegate的方向支持的方法(我觉得最靠谱的一个)。

    如果页面只是很简单的业务,就用第一种。
    方式一: 监听设备旋转方向, 改变当前view的transform

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deviceOrientationDidChange) name:UIDeviceOrientationDidChangeNotification object:nil];
    - (void)orientationChange:(BOOL)landscapeRight
    {
        CGFloat width = self.view.frame.size.width;
        CGFloat height = self.view.frame.size.height;
        if (landscapeRight) {
            [UIView animateWithDuration:0.25f animations:^{
                self.view.transform = CGAffineTransformMakeRotation(-M_PI_2);
                self.view.bounds = CGRectMake(0, 0, height, width);
            }];
            [self enterLandscapeScreen];
        } else {
            [UIView animateWithDuration:0.25f animations:^{
                self.view.transform = CGAffineTransformIdentity;
                self.view.bounds = CGRectMake(0, 0, width, height);
            }];
            [self exitLandscapeScreen];
        }
        if([self respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]){
            [self prefersStatusBarHidden];
            [self performSelector:@selector(setNeedsStatusBarAppearanceUpdate)];
        }
    }
    

    方式二:重写以下几种方法,通过present的方式显示就可以让控制器横屏,但是如果涉及到键盘输入,就需要将键盘进行旋转

    
    - (BOOL)shouldAutorotate {
        
        return false;
    }
    - (UIInterfaceOrientationMask)supportedInterfaceOrientations {
        return UIInterfaceOrientationMaskLandscapeLeft;
    }
    
    - (UIInterfaceOrientation)preferredInterfaceOrientationForPresentation {
        
        return UIInterfaceOrientationLandscapeLeft;
    }
    
    
    • 键盘旋转成横屏, iOS9.0以后是根据第三个remoteWindow控制键盘方向的。这里有一个小问题,就是拿不到键盘准确的高度,我尝试使用了通知和获取window中的view,都没找到准确的高度,所有我使用的是第三种横屏方式;
     let windows = UIApplication.shared.windows
            let deviceSize  = UIScreen.main.bounds.size;
            if windows.count == 3 {
                let window = windows.last;
                window?.bounds = CGRect.init(x: 0, y: 0, width: deviceSize.width, height: deviceSize.height)
                window?.transform = CGAffineTransform.init(rotationAngle: CGFloat(Double.pi / 2.0))
            }
    

    方式三:交换delegate的supportedInterfaceOrientationsForWindow方法,切记要在离开这个页面的时候,在调用一次交换方法 ,也就是把方法 又交换回去。

    - (void)setupOrientationConfig {
        UIApplication *application = [UIApplication sharedApplication];
        id appdelegate = application.delegate;
        
        SEL implSelector = @selector(ncn_application:supportedInterfaceOrientationsForWindow:);
        
        Method originalMethod = class_getInstanceMethod([appdelegate class], @selector(application:supportedInterfaceOrientationsForWindow:));
        if (originalMethod == nil) {
            //appdelegate必须实现  这个方法
            NSAssert(0, @"appdelegate must define application:supportedInterfaceOrientationsForWindow:");
        }
        Method newMethod = class_getInstanceMethod(self.class, implSelector);
        
        method_exchangeImplementations(originalMethod, newMethod);
        
    }
    
    - (UIInterfaceOrientationMask)ncn_application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
        
        return UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskPortrait;
        
    }
    //手动触发 设备方向改变 
     if ([[UIDevice currentDevice]   respondsToSelector:@selector(setOrientation:)]) {
                              [[UIDevice currentDevice] setValue:[NSNumber numberWithInteger:UIInterfaceOrientationLandscapeLeft]
                                                           forKey:@"orientation"];
                            }
    

    // 在横屏状态下 回到桌面再进入app,并且返回到上一页面的时候,有可能设备不会自动进行旋转,这时候可以在需要旋转的时候调用attemptRotationToDeviceOrientation

    UIViewController.attemptRotationToDeviceOrientation()
    

    5、UISegmentedControl选中背景颜色适配

     if #available(iOS 13.0, *) {
                self.titleSegment.selectedSegmentTintColor = .HWColorWithHexString(hex: "#639FF8")
                self.titleSegment.tintColor = UIColor.white;
    
            } else {
    //setBackgroundImage  没有用 
                self.titleSegment.tintColor = UIColor.HWColorWithHexString(hex: "#639FF8");
            };
    

    6、一个很好用的图片编辑插件 LFMediaEditingController!!

    - (void)editImage:(UIImage *)image {
        
    
        LFPhotoEditingController *vc = [[LFPhotoEditingController alloc] initWithOrientation:UIInterfaceOrientationPortrait];
        [vc setEditImage:image];
        vc.delegate = self;
        UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
        [self presentViewController:nav animated:true completion:nil];
        
        
    }
    

    7、运存app -> 内存一直增加到崩溃为止

    碰到这种问题根本看不到任何错误日志,最笨的办法就是一行行代码去运行检查或者使用instruments,因为这个时候的CPU是全力运行的。我今天就碰到这样一个问题,我使用的是前者的解决办法,花了两个小时排查发现是因为给label的text赋值了一个null对象,这是因为后台接口编写不规范,需要的是个字符串,没值就返回了个null对象。当然前端也可以过滤,但是我就是不想,我很生气!

    8、用UIBezierPath画带箭头的直线

    3031591951212_.pic.jpg
    - (void)graphStrightLineArrow:(CGPoint)endpoint {
        
        UIBezierPath *graphPath = [UIBezierPath bezierPath];
        [graphPath moveToPoint:self.startPoint];
        [graphPath addLineToPoint:endpoint];
        CGFloat distance = 40 * self.pointpXRatio;
        
        //弧度
        CGFloat radian = (60 * M_PI) / 180;
    
        //正切
        CGFloat y1=(endpoint.y - self.startPoint.y);
        CGFloat x1=(endpoint.x - self.startPoint.x);
        CGFloat tangent = -atan2(y1, x1);
    
        //右边线
        CGFloat point3x = endpoint.x + distance * sin(tangent - radian);
        CGFloat point3y = endpoint.y + distance * cos(tangent - radian);
        [graphPath addLineToPoint:CGPointMake(point3x, point3y)];
        [graphPath moveToPoint:endpoint];
        
        //左边线
        CGFloat point4x = endpoint.x + distance * sin(tangent + M_PI + radian);
        CGFloat point4y = endpoint.y + distance * cos(tangent + M_PI + radian);
        [graphPath addLineToPoint:CGPointMake(point4x, point4y)];
        self.path = graphPath.CGPath;
        
    }
    
    

    9、去掉TabBar的黑线

    试了网上的很多方案都不好使,还是自己看图层靠谱。

    在TabBarVC的viwDidAppear里面 添加以下代码

    for (int i = 0; i < self.tabBar.subviews.count; i++) {
            UIView *view = self.tabBar.subviews[I];
            NSString *classstr = NSStringFromClass(view.classForCoder);
            if ([classstr isEqualToString:@"_UIBarBackground"] || view.frame.origin.y == -0.5) {
                view.backgroundColor = UIColor.whiteColor;
            }
        }
    

    10、Failed to connect to github.com port 443: Operation timed out 终端ping github 超时解决办法

    网上试了很多办法,都不行,最后找到是由于本地DNS解析域名失败,本地有一个域名ip 地址映射表,修改为正确的ip就解决问题了。

    1. 查询github IP, 输入 github.com
    image.png

    2、打开终端,输入命令 sudo nano /private/etc/hosts 找到github.com 那行 然后修改为查询的ip。

    image.png

    3、再次ping就通了


    image.png

    如果失败,多尝试几次第一步骤, 因为 github.com 域名的ip 会变。

    11、本地pod混编库引用的问题

    • 本地库中的swift类,在外面引用不到。

    这是因为swift中的类在module中默认是受保护的,在同一个module中是共享的,但是如果想在另外一个module使用这个类,必须使用public关键字修饰。
    真的要疯,这个问题困扰了我将近两天,一直以为是我podspec的设置问题,没找到解决问题的关键点;其实就是自己的swift基础太差,人太蠢。

    • 在项目中使用库中的swift类中引用的一个OC类报错,该类没有这个属性

    因为我在项目的OC桥接文件中引用这个库的OC相关类,这也就造成了,把这个库的所有OC类桥接到项目的module中去了,因此原库的module中就没有那些OC类了,所以才会报错。

    12、获取手机连接WiFi的mac地址问题

    • 12.0以后需要在证书添加wifi information支持
    • 在获取的时候必须定位之后才能获取到(搞不明白为什么)
      以下是获取wifi的mac地址代码,ssid是名称 bssid是Mac地址
        func getWiFiBSsid() -> String? {
            var bssid: String?
            if let interfaces = CNCopySupportedInterfaces() as NSArray? {
                for interface in interfaces {
                    if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
                        bssid = interfaceInfo[kCNNetworkInfoKeyBSSID as String] as? String
                        break
                    }
                }
            }
            return bssid
        }
    

    13、Include of non-modular header inside framework module

    • 两个本地库相互依赖的时候,其中一个库在.h文件中 声明另外一个库的文件报错,.m文件中引用就没有问题;
    • 我查了网上很多的解决办法都不好使,最后我在添加了相关设置之后,写全了文件的路径就编译成功了,如:#import "库A/类名.h", 没有提示也没事;

    14. Swift JSONDecoder 解析json为结构体失败

    由于写法的不同可能导致结构体数据为空。
    开发环境: swift 5.0 xcode:12.5.1

    • 失败写法:
      let info = try? JSONDecoder.init().decode(UserInfo.self, from: data!)
      
    • 成功写法:
        var info: UserInfo?
        do {
           info = try JSONDecoder.init().decode(UserInfo.self, from: data!)
         } catch  {
           print(error)
         }          
             
      

    具体原因不明,可能因为swift语言版本原因!

    15. 在swift 本地pod库中的xib中,使用bundle中的图片,显示不出来的问题

    开发环境: swift 5.0 xcode:12.5.1

    • 在使用OC 本地pod库,解决办法是将xib和要用到的图片放到一个bundle中,就可以解决该问题;
    • 在使用swift 本地pod库,解决办法在xib中的前面加上该图在bundle中的路径,bundle也要加上。如:ExPApp.bundle/Assets/common/subject_symbol.png
    • 在.podspec文件中,将图片和xib放到一个bundle中的写法是:
        'ExPApp' => ['ExPApp/Assets',"Classes/**/*.xib",
        "Classes/**/**/*.xib",
        "Classes/**/**/**/*.xib",
        "Classes/**/**/**/**/*.xib",]
      }
    

    但是在swift的pod库中,就算这么写。xib也不会被放到bundle中

    16. Swift 中 UILabel显示html内容的写法

                let html = _model.subjects ?? ""
                  do {
                      if let data = html.data(using: String.Encoding.unicode, allowLossyConversion: true) {
                          let attStr = try NSAttributedString.init(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html,], documentAttributes: nil)
                          self.subjectLabel.attributedText = attStr
                      }
                  } catch {
                      self.subjectLabel.text = html
                  }
    

    17. 给UIButton设置图片或者UILabel设置内容导致CPU跑到100%,内存直接撑到程序崩溃?

    • UIButton可能的原因,将button以addSubView的方式添加到了UIStackView中,并且设置了图片。应该使用addArrangedSubview;
    • UILabel可能的原因,在OC中可能出现赋值的类型不是一个字符串,可能是其他的对象,比如从网络解析到了一个NSNull对象赋值给label的text;

    18. Cocoapods 组件化(本地库)内 OC 调用swift

    #import <YourSDK/YourSDK-Swift.h> 
    

    Swift 类必须声明为public

    19. 执行pod init 报错:RuntimeError - [Xcodeproj] Unknown object version.

    更新pod版本就行了,执行命令:sudo gem install cocoapods --pre

    20. 将UIButton添加到一个UIStackView中的时候,button的size设置无效的问题;

    解决方法一: 使用layout方式给button设置size
    解决方法二: 重写button的intrinsicContentSize方法设置size

    21. pod update 报错 Couldn't determine repo type for URL: 'xxxxxx.git'

    问题背景:某个第三方库需要进行更新,而且source原地址也进行了变更。在更新时,应该是新版本的repo跟旧版本的repo冲突,造成了报错;
    解决办法:删除本地的相关源的repo,然后重新pod update就ok了;

    本地repo库路径: ~/.cocoapods/repos/  
    手动删除或者执行命令删除指定repo:pod repo remove 库名称
    

    22. 关于结构体字节对齐的问题

    背景:最近在做TCP交互,在解析包头的时候,我使用的是结构体进行定义解析的,我们定的协议头是21个字节。但是在分配内存的时候,根据字节对齐是按照结构体中类型最大的进行对齐,我这边最大的是指针4个字节。所以分配的是24个字节;

    struct XXXXXXXXHeader {
        
        /// 协议头名称
        char name[8];
        /// 版本号
        uint8_t version;
        /// 设备类型  01 == Android  02 == iOS  03 == other
        uint8_t deviceType;
        /// 消息类型 0x01 == 请求  0x02 == 回复  0xff == 心跳
        uint8_t message_type;
        /// 主业务id
        uint8_t main_id;
        /// 子业务id
        uint8_t sub_id;
        /// 消息id
        uint32_t session_id;
        /// 发送的数据长度
        uint32_t content_length;
    };
    

    问题:在发送消息时对包头序列化时,最后一个content_length,总是解析错误,明明是0,却总是解析出一些乱七八糟的值。原因:根本问题是因为栈溢出了,在读取content_length的值,会多读取另外3个字节的地址值;
    解决办法:让结构体的字节对齐以1个字节对齐,这样在给结构体分配内存的时候,只会分配21个字节。使用#pragma pack();

    #pragma pack(push,1)
    struct XXXXXXXXHeader {
        
        /// 协议头名称
        char name[8];
        /// 版本号
        uint8_t version;
        /// 设备类型  01 == Android  02 == iOS  03 == other
        uint8_t deviceType;
        /// 消息类型 0x01 == 请求  0x02 == 回复  0xff == 心跳
        uint8_t message_type;
        /// 主业务id
        uint8_t main_id;
        /// 子业务id
        uint8_t sub_id;
        /// 消息id
        uint32_t session_id;
        /// 发送的数据长度
        uint32_t content_length;
    };
    #pragma pack(pop)
    

    23. perfromSelector 多参数传递

    我的应用场景:单例实现多代理,在调用代理方法时,封装一个公共方法处理调用。这个方法只需要接受两个参数,一个是action:Selector,另一个是数组:数组中顺序存放action中需要传递的参数;
    解决办法:通过NSInvocation处理
    代码如下:

    - (void)notifiyDelegateWithAction:(SEL)action withObjects:(NSArray *)objs {
        
          dispatch_async(self.delegateQueue, ^{
            
            for (NSObject *obj in self.delegates) {
                
                if ([obj respondsToSelector:action] && obj) {
                    
                    NSMethodSignature *signature = [obj methodSignatureForSelector:action];
                    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
                    
                    [invocation  setTarget:obj];
                    [invocation setSelector:action];
                    
                    for (int i = 0; i < objs.count; i++) {
                        
                        NSObject *value = objs[i];
                        // 因为每个方法都有两个隐式参数 一个是调用方法的对象self 一个是调用的方法名称_cmd,所以从第2个开始设置参数
                        [invocation setArgument:&value atIndex:i + 2];
                    }
                    // 强引用所有参数
    //                [invocation retainArguments];
                    
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [invocation invoke];
                    });
                }
            }
        });
       
        
    }
    
    

    再传参数的时候有个坑 如果形参是基本数据类型(比如枚举),就只能用基本数据类型定义,再把指针传进去。参数才能正确拿到

     NSObject *value = objs[i];
     if ([value isKindOfClass:NSNumber.class]) {
          int intValue = [(NSNumber *)value intValue];
          [invocation setArgument:&intValue atIndex:i + 2];
    }
    

    24. 在CALayer 绘制图片是倒着的问题

    在绘制的时候上下文的ctm默认是这样的:
    a 是scaleX d是scaleY

    (CGAffineTransform) $R0 = {
      a = 1
      b = 0
      c = 0
      d = -1
      tx = 0
      ty = 0
    }
    

    然后再调用drawImage的时候,ctm的ty会变成负数,这样就在Y轴上进行翻转了;
    解决:在绘制前将ty正向偏移一个图片的高度和scaleY设置成1,就可以解决

        override func draw(in ctx: CGContext) {
            
            let image = UIImage.init(named: self.imageUrl) ?? UIImage.init(contentsOfFile: self.imageUrl)
            if let cgImage = image?.cgImage {
                // 默认scaleY 是-1.0  
                ctx.translateBy(x: 0, y: self.bounds.size.height)
                ctx.scaleBy(x: 1.0, y: -1.0)
                ctx.draw(cgImage, in: .init(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height));
                ctx.restoreGState();
            }
        }
    
    

    25. 组件化开发之OC与Swift混编,在swift中无法使用OC类中的成员变量或者函数;

    错误信息:may not be available in this context
    错误原因:在pod本地库A中用到另外一个库B的类型时,不管是用作变量还是函数的形参,在项目swift中用这个库A类的该变量或者函数,会提示找不到。因为在swift中链接不到在库A使用的库B的那个类型;总结来说就是没有穿透链接...
    解决办法: 在库A中将用的库B类型涉及的那个文件设置成prefix;

    network_spec.prefix_header_contents = <<-DESC
        #import "AFURLRequestSerialization.h"
    DESC
    

    25. 组件化混编之在OC项目中的OC中调用本地OC库中的swift

    描述: pod本地库是混编的,有OC也有swift,在项目中的OC需要用到pod本地库中的swift类;
    解决办法:在OC类中需要访问swift的#import方式改为@import YourSDK.swift,就可以访问库中所有的swift类了;
    条件:pod库的swift类需要声明为 public
    环境:XCode version = 13.2 ; swift version = 5.0;

    pod库的文件目录如下:


    image.png

    26. 代理方法不走,项目一启动,断点状态会变成无效;

    如图:


    image.png

    原因: 因为swift版本不同的原因,可能是你从其他项目拷贝过来的代码;只要是方法的类型不一样,这个代理方法也不会走。可能断点状态的变化,算是xcode给的提示吧;

    持续更新,大家也可以将自己踩的坑发给我,进行更新补充;

    相关文章

      网友评论

          本文标题:iOS开发 小技巧 + 踩坑记

          本文链接:https://www.haomeiwen.com/subject/lbutohtx.html