【iOS】常用技术点小记1

layer.shouldRasterize

shouldRasterize instructs Core Animation to cache the layer contents as an image.
栅格化,设置为true则Core Animation将layer.contents渲染成一张图片缓存下来备用,不再刷新重绘视图,从而提高效率

layer.shouldRasterize = true 
layer.rasterizationScale = UIScreen.main.scale

layer.anchorPoint

设置layer.anchorPoint.x 要在设置layer.frame之前

UIViewController-自定义初始化方法

init(sideMenu: UIViewController, center: UIViewController) {
    menuViewController = sideMenu
    centerViewController = center
    super.init(nibName: nil, bundle: nil)
  }

CAGradientLayer

CAGradientLayer

debug UIView Hierarchy

这将会显示所有view的层级结构,包括超出屏幕的、透明的、隐藏的

NSLayoutConstraint

    // fixed width constraint:
    let _ = NSLayoutConstraint(item: iv, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 50)
    
    // relative width constraint
    let widthCons = NSLayoutConstraint(item: iv, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 1/3, constant: -50)

代码查找约束

NSLayoutConstraint
for cons in titleLabel.superview!.constraints {
        if cons.firstItem as? UIView == titleLabel && cons.firstAttribute == .centerX{
            cons.constant = isMenuOpen ? 100 : 0
            break
        }
}

swift3 CGAffineTransform

view.transform = CGAffineTransform(scaleX: 1, y: 0.1)
.concatenating(CGAffineTransform(translationX: 0, y: off))

Array扩展

extension Array where Element: Comparable{

}

guard & where 配合使用

guard let dividend = dividend where dividend != 1, let divisor = divisor where divisor != 0 else {
     return .None
}

播放声音

AudioServicesPlayAlertSound(kSystemSoundID_Vibrate) //震动

获取String高度/宽度

let rect = (textView.text as NSString).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: textView.font!], context: nil)

UIColor-16进制

public convenience init(hex: Int){
    self.init(red: CGFloat((hex >> 16) & 0xff), green: CGFloat((hex >> 8) & 0xff), blue: CGFloat(hex & 0xff), alpha: 1)
}

UITableView分割线设置问题

// 注意在initWithFrame/initWvithCoder里设置才起作用
v.separatorStyle = .none

UITableView/UICollectionView阴影设置问题

v.clipsToBounds = false //必须为false才行,默认是true
// cell也要与view设置同样大的圆角,同时bgcolor设置为nil

UICollectionViewCell/UITableViewCell布局layoutMargins设置注意

// 必须设置了contentView.layoutMargins才起作用
contentView.layoutMargins = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)

Xcode8一直打印AQDefaultDevice (173): skipping input stream

Product -> Scheme -> Edit Scheme

libc++abi.dylib`__cxa_throw: 使用[AVAudioPlayer play]会产生__cxa_throw异常

All包含__cxa_throw异常捕获
只捕获objc异常

Swfit.AnyClass获取

public var appName: String{
        guard let dict = Bundle.main.infoDictionary,
            let name = dict["CFBundleName"] as? String else{
                return ""
        }
        return name
}
func SwiftClassFromNameString(_ className: String) -> AnyClass?{
    let name = "\(appName).\(className)"//这里要加上AppName.
    return NSClassFromString(name)
}

WKWebView与JS交互

<script type="text/javascript">
      function test() {
        window.webkit.messageHandlers.Test.postMessage({body: '是否退出登录?'});
      }
    </script>
import UIKit
import WebKit

class ViewController: UIViewController, WKScriptMessageHandler {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        let config = WKWebViewConfiguration()
        let user = WKUserContentController()
        user.add(self, name: "Test")
        config.userContentController = user
        let wk = WKWebView(frame: view.bounds, configuration: config)
        
        wk.load(URLRequest(url: Bundle.main.url(forResource: "test.html", withExtension: nil)!))
        
        view.addSubview(wk)
    }

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        print(message.body)
    }

}

JS判断客户端类型

// 01
<script type="text/javascript"> 
        var u = navigator.userAgent; 
        var isAndroid = u.indexOf('Android') > -1 || u.indexOf('Adr') > -1; //android终端 
        var isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端 
        if (isAndroid) {
            alert('这是Android'); 
        }
        if (isiOS) {
            alert('这是IOS'); 
        }
    </script>
// 02
<script type="text/javascript">
        if (/(iPhone|iPad|iPod|iOS)/i.test(navigator.userAgent)) {
            //alert(navigator.userAgent);
            alert('这是IOS');
        } else if (/(Android)/i.test(navigator.userAgent)) {
            //alert(navigator.userAgent);
            alert('这是Android');
        } else {
            alert('这是PC');
        };
    </script>

UIWebView/WKWebView视频播放问题

allowsInlineMediaPlayback = true
mediaPlaybackRequiresUserAction = !UIDevice.current.isIpad //iPad得设置为false

状态栏隐藏

app.setStatusBarHidden(true, with: .fade) //9.0以前
// 或
override var prefersStatusBarHidden: Bool{
     return true
}
全局配置

View指定圆角

    @IBInspectable
    public var topLeft: Bool = false{
        didSet{
            setNeedsDisplay()
        }
    }
    
    @IBInspectable
    public var bottomLeft: Bool = false{
        didSet{
            setNeedsDisplay()
        }
    }
    
    @IBInspectable
    public var topRight: Bool = false{
        didSet{
            setNeedsDisplay()
        }
    }
    
    @IBInspectable
    public var bottomRight: Bool = false{
        didSet{
            setNeedsDisplay()
        }
    }
    
    @IBInspectable
    public var radius: CGFloat = 0{
        didSet{
            setNeedsDisplay()
        }
    }
override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        var corners: UIRectCorner = []
        if topLeft {
            corners.insert(.topLeft)
        }
        if bottomLeft {
            corners.insert(.bottomLeft)
        }
        if topRight {
            corners.insert(.topRight)
        }
        if bottomRight {
            corners.insert(.bottomRight)
        }
        let path = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let maskLayer = CAShapeLayer()
        maskLayer.path = path.cgPath
        maskLayer.frame = rect
        layer.mask = maskLayer
    }

clipsToBounds和layer.masksToBounds对shadow效果的影响

  • 注意true、false值对shadow效果的影响
  • 注意layer.cornerRadius和其他border属性对shadow的影响

iOS10.3应用内动态替换AppIcon

Info.plist配置
    @IBOutlet weak var segment: UISegmentedControl!
    
    fileprivate let icons = [
        "AppIcon1",
        "AppIcon2"
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()

       // 上次设置的图标名,默认为nil
        if let name = UIApplication.shared.alternateIconName,
            let i = icons.index(of: name){
            print(name)
            segment.selectedSegmentIndex = i+1
        }
    }

    @IBAction func changeIcon(_ sender: UISegmentedControl){
        let app = UIApplication.shared
        let can = app.supportsAlternateIcons
        if can {
            let index = sender.selectedSegmentIndex
            // nil时为默认图标Primary Icon
            let name =  index == 0 ? nil : icons[index-1] 
            
            app.setAlternateIconName(name, completionHandler: { (error) in
                let info = error != nil ? "失败" : "成功"
                print("更好图标: \(info)")
            })
        }
        else{
            print("不支持 AppIcon 动态替换")
        }
    }

UIView按下样式

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        // 背景色
        backgroundColor = mcolors.gray.withAlphaComponent(0.2)
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        // 背景色
        backgroundColor = mcolors.white
    }

UICollectionViewCell的shadow问题


    cell.layer.masksToBounds = false; //必须为false否者阴影没有效果

    cell.layer.contentsScale = [UIScreen mainScreen].scale;

    cell.layer.shadowOpacity = 0.75f;

    cell.layer.shadowRadius = 4.0f;

    cell.layer.shadowOffset = CGSizeMake(0,0);

    cell.layer.shadowPath = [UIBezierPath bezierPathWithRect:cell.bounds].CGPath;

    //设置缓存
    cell.layer.shouldRasterize = YES;

    //设置抗锯齿边缘
    cell.layer.rasterizationScale = [UIScreen mainScreen].scale;

点击status bar上的“返回 xxx”应用的处理,刷新controller数据

  • 注意,之前的vc将不再调用viewWillAppear方法,需要手动利用通知中心处理事件
func applicationWillEnterForeground(_ application: UIApplication) {
        // 通知
        NotificationCenter.default.post(name: .xxx, object: nil)
}

The Copy Bundle Resources build phase contains this target's Info.plist问题

删除plist

关于输入框键盘遮挡动画的一种不错方法

  • 封装包含或基于UITextField的控件
// 加入键盘监听
[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillShow:)
                                                     name:UIKeyboardWillShowNotification
                                                   object:nil];
        
[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillHide:)
                                                     name:UIKeyboardWillHideNotification
                                                   object:nil];
// 处理键盘事件
-(void)keyboardWillShow: (NSNotification *)noti{
    CGRect frame = [noti.userInfo[@"UIKeyboardFrameEndUserInfoKey"] CGRectValue];
    CGFloat y = UIScreen.mainScreen.bounds.size.height - frame.size.height;
    
    CGFloat bottom = CGRectGetMaxY(self.frame);
    
// 只有输入框被键盘遮挡时才调用
    if (bottom > y) {
        
        CGFloat off = bottom - y;
        CGFloat duration = [noti.userInfo[@"UIKeyboardAnimationDurationUserInfoKey"] floatValue];
        [_delegate inputView:self shouldUpdatePositionY:off duration:duration];
    }
}

-(void)keyboardWillHide: (NSNotification *)noti{
    CGFloat duration = [noti.userInfo[@"UIKeyboardAnimationDurationUserInfoKey"] floatValue];
    
    [_delegate inputView:self shouldUpdatePositionY:0 duration:duration];
}

// 代理方法,改变约束值,进行动画
-(void)inputView:(HYLoginInputView *)inputView shouldUpdatePositionY:(CGFloat)offsetY duration:(NSTimeInterval)duration{
    
// 存储变量,变化值
    _offYKeyboard = offsetY;
    
    [UIView animateWithDuration:duration animations:^{
        [self layoutSubviews]; //注意在UIViewController中调用[self layoutIfNeeded];进行重新布局
    }];
}

// 更新布局
-(void)layoutSubviews{
    [super layoutSubviews];

    [_userNameInput mas_updateConstraints:^(MASConstraintMaker *make) {
        make.leading.equalTo(@(offX));
        make.trailing.equalTo(@(-offX));
        make.centerY.mas_equalTo(self).offset(-_offYKeyboard); //键盘变化数值
        make.height.equalTo(@80);
    }];

}

关于Assets中image的Slicing

示例

[The app delegate must implement the window property if it wants to use a main interface]解决

  • ✅ AppDelegate中设置
   var window: UIWindow!
   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window.rootViewController = xxx
        return true
    }
  • 不要试图手动维护某个window,并设置为keyWindow,否则导致navigationBar的高度为44或横竖屏显示错误的问题,如:
private let window = UIWindow(frame: UIScreen.main.bounds)
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        window.rootViewController = xxx
        return true
    }
然后设置`Main Interface`为某个storyboard

[POST git-receive-pack (chunked)]解决

SourceTree 操作1
SourceTree 操作2
[http] 
    postBuffer = 524288000

默认Xcode设置

xcode-select --print-path //查看原先路径

sudo xcode-select --switch
/Applications/Xcode8.3.2/Xcode8.3.2.app/Contents/Developer //新路径

横竖屏控制UICollectionView的不同尺寸展示

override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) {
        super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator)

//改变布局 size,走UICollectionViewDelegateFlowLayout方法
        collectionView?.collectionViewLayout.invalidateLayout() 
//刷新数据 cell,走UICollectionViewDataSource方法
        collectionView?.reloadData() 
    }

// 尺寸控制
func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize {
        let num: CGFloat = app.statusBarOrientation.isLandscape ? 5 : 4
        let w = (view.bounds.width - (num+1)*itemSpace) / num

        return CGSize(width: w, height: w)
    }

// 显示个数控制
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        let num = app.statusBarOrientation.isLandscape ? 6 : 5
        let count = data[section].items.count
        if count > num {
            return num
        }
        return count
    }

It looks like git-am is in progress. Cannot rebase

cd 到工程根目录
执行rm -rf .git/rebase-apply

利用CIDetector识别图中的二维码

  • 只适用于真机
  • iOS5.0及以上
// 截屏图片
let screen = UIScreen.mainScreen()
        UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, false, screen.scale);
        self.view.layer.renderInContext(UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext();
// 识别
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
        let ciimage = CIImage(CGImage: image.CGImage!)

        let features = detector.featuresInImage(ciimage)
        if let feature = features.first as? CIQRCodeFeature{
            let str = feature.messageString
            print("result: \(str)")
        }

HUD提示view被键盘遮挡的解决方法

  • 原因:将view加在了UIApplication.shared.keyWindow上,而键盘弹出时又是一个新的类型为UIRemoteKeyboardWindow的窗口
  • 解决:每次去动态计算获取相应的keyWindow()
func keyWindow() -> UIWindow{
    var window = app.keyWindow!
    for w in app.windows{
        let name = NSStringFromClass(w.classForCoder)
        if name == "UIRemoteKeyboardWindow"{
            window = w
            break
        }
    }
    return window
}

TableView删除cell问题小记

cell.configureCell(data: classes[indexPath.row]) {[unowned self] (data) in
            var index: IndexPath!
// 注意这里存储的indexPath将不再有用,因为随着cell的删除,indexPath值是变化的,
// 除非你自己手动在删除cell后reloadData(),但那样会失去动画效果
            for (i,e) in self.classes.enumerated(){
                if e.id == data.id{
                    index = IndexPath(row: i, section: 0)
                    break
                }
            }
            self.classes = self.classes.filter{
                $0.id != data.id
            }
            self.tableView.deleteRows(at: [index], with: .automatic)
        }

TabBar 相关尺寸

icon: <= 30x30px 一般: 26x26px
height: 49px = 48px + 1px分割线
fontSize: 12px

UITextField限定输入长度

extension RegisterVC: UITextFieldDelegate{
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        if string.length == 0 { //删除键
            return true
        }
        
        if textField == xxxTF && text.length > 10 {
            return false
        }
        return true
    }
}

Swift3.0 private、fileprivate、internal、public、open修饰符

  • private 定义的方法和属性,只能是该类的对象可以访问,类的extension子类对象中均无法访问。

  • fileprivate 顾名思义,只在当前的.swift文件中是私有的,可以在多个文件中定义同名的变量值。

  • internal 是默认的修饰符,可在当前整个工程源码所在的所有文件中均可以访问,常用来定义一些全局的宏变量或非法,通常省略不写。

  • public 只能在module内部被inherit和override

  • open 任何地方均可访问和override

Swift 参数是Float或Int类型的函数定义

func fo<T>(_ value: T) where T: ExpressibleByIntegerLiteral, T: ExpressibleByFloatLiteral {
        print(value)
}

Swift 输出函数名、类名、方法所在行号

func fo(name: String = #function, line: Int = #line, file: String = #file) {
        print("\(file.lastPathComponent.deletingPathExtension) - \(name) - \(line)")
}

Swift3.0生成二维码图片


public extension UIImage {  
      
    public class func createQRCode(code: String, width: CGFloat, height: CGFloat) -> UIImage? {  
        let data = code.data(using: String.Encoding.isoLatin1, allowLossyConversion: false)  
        if let filter = CIFilter(name: "CIQRCodeGenerator") {  
            filter.setValue(data, forKey: "inputMessage")  
            filter.setValue("H", forKey: "inputCorrectionLevel")  
//            inputCorrectionLevel 是一个单字母(@"L", @"M", @"Q", @"H" 中的一个),表示不同级别的容错率,默认为 @"M"  
//            错误修正容量 L水平 7%的字码可被修正  
//            M水平 15%的字码可被修正  
//            Q水平 25%的字码可被修正  
//            H水平 30%的字码可被修正  
//            所以很多二维码的中间都有头像之类的图片但仍然可以识别出来就是这个原因  
            if let QRCodeImage = filter.outputImage {  
                //消除模糊  
                let scaleX = width/QRCodeImage.extent.size.width  
                let scaleY = height/QRCodeImage.extent.size.height  
                let transformedImage = QRCodeImage.applying(CGAffineTransform.init(scaleX: scaleX, y: scaleY))  
                  
                return UIImage(ciImage: transformedImage)  
            } else {  
                return nil  
            }  
        }else {  
            return nil  
        }  
    }  
}  

StoryBoard上的UITableViewController中的static cell无法设置layoutMargins

如图设置cell元素约束的时候,必须考虑默认的值为8的margin

改变无效
.leading = 12+8

关于storyboard的unwind

//unwind一定是定义在当前页面vc的来源vc中,
//如vc2是由vc1导航push进来的,那么该unwind方法就应该在vc1中,而不是v2中

@IBAction func unwindForVC1(_ sender: UIStoryboardSegue){
        
}

MathJax数学表达式解析

<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML"></script>

hidesBottomBarWhenPushed=true使得view在布局变化时往下掉的问题

问题

解决方法:

tabBar.isTranslucent = false //设置为不透明
同样适合解决:navigationController.toolBar.hidden = false出现的同样问题

xib或storyboard自动布局

当需要根据子view的高度压缩或拉伸的时候,就得设置各个view的Hugging PriorityCompression Resistance Priority
值越大越难被拉伸Hugging或压缩Compression

要实现如图的效果则需要如下设置:


拉伸-压缩布局

则布局设置优先级为:

green view
skin view

点击view隐藏键盘的2种方法

//1. 当前view的touchesBegan方法
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesBegan(touches, with: event)
        endEditing(true)
    }

//2. 找不到controller或editing事件不在当前view中
UIApplication.shared.keyWindow?.endEditing(true)

brew update

sudo chmod -R g+w /usr/local
brew update

libxml2.tbd库引入问题解决

报错: 'libxml.h' file not found with <angled> include, use "quotes" instead.

解决方法

在`Build Settings`里设置

Podfile编写

inhibit_all_warnings!

target 'DemoKissXML' do
    platform :ios, '7.0'
    pod 'KissXML'
end
  • 执行命令:pod setup ,再执行 pod install,每次改变Podfile内容,执行pod update

StoryBoard上自动布局约束设置

设置间距space约束
space to

指定UIView的LayoutMargins

// 默认为8dp - 定义了view的内边距
view.layoutMargins = UIEdgeInsetsMake(20, 20, 20, 20)
storyboard设置

UIWebView和JS交互一种新方案:

// 通过JS向body中新插入一个iframe标签,就会触发OC的webView shouldStartLoadWithRequest方法,
// 从而传递来自html的参数达到和UIWebView交互的目的

//JS方法
      <script language="javascript">
            function loadURL(url) {
                var iFrame;
                iFrame = document.createElement("iframe");
                iFrame.setAttribute("src", url);
                iFrame.setAttribute("style", "display:none;");
                iFrame.setAttribute("height", "0px");
                iFrame.setAttribute("width", "0px");
                iFrame.setAttribute("frameborder", "0");
                document.body.appendChild(iFrame);
                // 发起请求后这个iFrame就没用了,所以把它从dom上移除掉
                iFrame.parentNode.removeChild(iFrame);
                iFrame = null;
            }
            function test() {
                loadURL("xxx:xxx"); //要传递的参数
            }
        </script>

// OC
- (BOOL)webView:(UIWebView *)webView 
shouldStartLoadWithRequest:(NSURLRequest *)request 
navigationType:(UIWebViewNavigationType)navigationType {
    NSURL * url = [request URL];
    if ([[url scheme] isEqualToString:@"xxx"]) {
        // do something...

        return NO;
    }
    return YES;
}

AppStore构建版本显示【缺少合规证明】

在info.plist中添加下面的键值
<key>ITSAppUsesNonExemptEncryption</key>
<false/>

Masonry中iOS10宏支持iOS7

// 在MASUtilities.h中加入
#define NS_NOESCAPE __attribute__((noescape))

自定义UIWebView右键菜单

// 自定义MyWebView,重写init方法
-(instancetype)initWithFrame:(CGRect)frame{
    self = [super initWithFrame:frame];
    if (self) {
// 这里自定义menu选项
        UIMenuItem *copy = [[UIMenuItem alloc] initWithTitle:@"拷贝" action:@selector(copy:)];
        UIMenuController *menu = [UIMenuController sharedMenuController];
        [menu setMenuItems:@[copy]];
    }
    return self;
}

// 重写方法
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
// 除了自定义menu的事件方法外,其他都不执行
    if (action == @selector(copy:)) {
        return YES;
    }
    return NO;
}

// copy事件
- (void)copy: (id)sender{
   [self copy: sender]; 
}

UIWebView网页文字下划线、标记颜色JS

function stylizeHighlightedString() {

    var range = window.getSelection().getRangeAt(0);
    var selectionContents = range.extractContents();
    var span = document.createElement("span");

    span.appendChild(selectionContents);

// 点击标记的文字事件
    span.setAttribute("onclick","alert('划过线的文字');");
// 标记颜色
    // span.style.backgroundColor  = "rgba(255,0,0,0.5)";
// 下划线
    span.style.borderBottom  = "2px solid red";

    range.insertNode(span);
}

UIWebView背景透明

// 前提是不能设置webview的分页模式为水平模式【.LeftToRight或.RightToLeft】
webView.backgroundColor = [UIColor clearColor];
webView.opaque = NO;

iOS设备分辨率【全】

【iPhone】
iPhone 1G 320x480
iPhone 3G 320x480
iPhone 3GS 320x480
iPhone 4 640x960
iPhone 4S 640x960
iPhone 5 640x1136
iPhone 5S 640x1136
iPhone 5C 640x1136
iPhone 6 750x1334
iPhone 6 Plus 1080x1920 (开发应按照1242x2208适配)
iPhone 6S 750x1334
iPhone 6S Plus 1080x1920 (开发应按照1242x2208适配)

【iPod Touch】
iPod Touch 1G 320x480
iPod Touch 2G 320x480
iPod Touch 3G 320x480
iPod Touch 4G 640x960
iPod Touch 5G 640x1136

【iPad】
iPad 1 1024x768
iPad 2 1024x768
The New iPad 2048x1536
iPad mini 1024x768
iPad 4 2048x1536
iPad Air 2048x1536
iPad mini 2 2048x1536
iPad Air 2 2048x1536
iPad mini 3 2048x1536
iPad mini 4 2048x1536
iPad Pro 2732x2048

Bugly自动上传符号表脚本

#!/bin/sh
#
# Copyright 2016 Bugly, Tencent. All rights reserved.
#
# V1.4.0
#
# 2016.08.03
#
#
#
######################################################
# 1. 脚本集成到Xcode工程的Target
######################################################
#
# --- Copy the SCRIPT to the Run Script of Build Phases in the Xcode project ---
#
# #
BUGLY_APP_ID="" #必填 appId
# #
BUGLY_APP_KEY="" #必填 appKey
# #
BUNDLE_IDENTIFIER="" #必填 bundleId
# #
UPLOAD_DSYM_ONLY=1
#
# # 脚本默认配置的版本格式为CFBundleShortVersionString(CFBundleVersion),  如果你修改默认的版本格式, 请设置此变量, 如果不想修改, 请忽略此设置
# CUSTOMIZED_APP_VERSION=""
#
# # Debug模式编译是否上传,1=上传 0=不上传,默认不上传
# UPLOAD_DEBUG_SYMBOLS=0
#
# # 模拟器编译是否上传,1=上传 0=不上传,默认不上传
# UPLOAD_SIMULATOR_SYMBOLS=0
#
# #只有Archive操作时上传, 1=支持Archive上传 0=所有Release模式编译都上传
# UPLOAD_ARCHIVE_ONLY=1
#
# #
# source dSYMUpload.sh
#
# --- END OF SCRIPT ---
#
#
#
#
#######################################################
# 2. 脚本根据输入参数处理
#######################################################
#
# #命令行下输入应用基本信息, .dSYM文件的父目录路径, 输出文件目录即可
#
# sh dSYMUpload.sh <bugly_app_id> <bugly_app_key> <app_bundle_identifier> <app_version> <dSYM_src_dir> <bSYMBOL_dest_dir>
#
# #
#
# #注意:
# # 1. dSYMUpload.sh会调用buglySymboliOS.jar进行.dSYM解析,所以依赖Java运行时环境
# # 2. dSYMUpload.sh和buglySymboliOS.jar的文件路径需一致
#
#

#
# --- CONTENT OF SCRIPT ---
#

# Bugly服务域名
BUGLY_DSYM_UPLOAD_DOMAIN="api.bugly.qq.com"

# 注意jar工具的路径跟dSYMUpload.sh脚本路径一致, 请务必保证jar路径的正确性
BUGLY_SYMBOL_JAR_PATH="dsymtool/buglySymboliOS.jar"
# 查找添加到系统目录的jar工具
if [ ! -f "${BUGLY_SYMBOL_JAR_PATH}" ]; then
BUGLY_SYMBOL_JAR_PATH="$HOME/bin/buglySymboliOS.jar"
fi

# 打印错误信息
function exitWithMessage(){
    echo "--------------------------------"
    echo "${1}"
    echo "--------------------------------"
    exit ${2}
}

# 上传bSYMBOL文件
function dSYMUpload() {
    P_APP_ID="$1"
    P_APP_KEY="$2"
    P_APP_BUNDLE_ID="$3"
    P_APP_VERSION="$4"
    P_BSYMBOL_ZIP_FILE="$5"
    
    #
    P_BSYMBOL_ZIP_FILE_NAME=${P_BSYMBOL_ZIP_FILE##*/}
        P_BSYMBOL_ZIP_FILE_NAME=${P_BSYMBOL_ZIP_FILE_NAME//&/_}
            P_BSYMBOL_ZIP_FILE_NAME="${P_BSYMBOL_ZIP_FILE_NAME// /_}"
            
            DSYM_UPLOAD_URL="https://${BUGLY_DSYM_UPLOAD_DOMAIN}/openapi/file/upload/symbol?app_id=${P_APP_ID}&app_key=${P_APP_KEY}"
            echo "dSYM upload url: ${DSYM_UPLOAD_URL}"
            
            echo "-----------------------------"
            STATUS=$(/usr/bin/curl -k "${DSYM_UPLOAD_URL}" --form "api_version=1" --form "app_id=${P_APP_ID}" --form "app_key=${P_APP_KEY}" --form "symbolType=2"  --form "bundleId=${BUNDLE_IDENTIFIER}" --form "productVersion=${BUGLY_APP_VERSION}" --form "fileName=${P_BSYMBOL_ZIP_FILE_NAME}" --form "file=@${P_BSYMBOL_ZIP_FILE}" --verbose)
            echo "-----------------------------"
            
            UPLOAD_RESULT="FAILTURE"
            echo "Bugly server response: ${STATUS}"
            if [ ! "${STATUS}" ]; then
            echo "Error: Failed to upload the zip archive file."
            elif [[ "${STATUS}" == *"{\"reponseCode\":\"0\"}"* ]]; then
            echo "Success to upload the dSYM for the app [${BUNDLE_IDENTIFIER} ${BUGLY_APP_VERSION}]"
            UPLOAD_RESULT="SUCCESS"
            else
            echo "Error: Failed to upload the zip archive file to Bugly."
            fi
            
            #Remove temp dSYM archive
            #echo "Remove temporary zip archive: ${DSYM_ZIP_FPATH}"
            #/bin/rm -f "${DSYM_ZIP_FPATH}"
            
            if [ "$?" -ne 0 ]; then
            exitWithMessage "Error: Failed to remove temporary zip archive." 0
            fi
            
            echo "--------------------------------"
            echo "${UPLOAD_RESULT} - dSYM upload complete."
            
            if [[ "${UPLOAD_RESULT}" == "FAILTURE" ]]; then
            echo "--------------------------------"
            echo "Failed to upload the dSYM"
            echo "Please check the script and try it again."
            fi
        }
        
        # .dSYM解析为bSYMBOL文件
        function dSYMParse() {
            DSYM_FILE="$1"
            DSYM_SYMBOL_FILE="$2"
            
            echo "--------------------------------"
            echo "Extract symbol info from .dSYM file. to ${DSYM_SYMBOL_FILE}"
            (/usr/bin/java -Xms512m -Xmx1024m -Dfile.encoding=UTF8 -jar "${BUGLY_SYMBOL_JAR_PATH}" -i "${DSYM_FILE}" -o "${DSYM_SYMBOL_FILE}" ) || exitWithMessage "Error: Failed to extract symbols." 1
            echo "--------------------------------"
            
        }
        
        # 执行
        function run() {
            
            CONFIG_BUGLY_APP_ID="$1"
            CONFIG_BUGLY_APP_KEY="$2"
            
            CONFIG_BUGLY_APP_BUNDLE_IDENTIFIER="$3"
            CONFIG_BUGLY_APP_VERSION="$4"
            CONFIG_DSYM_SOURCE_DIR="$5"
            CONFIG_DSYM_DEST_DIR="$6"
            CONFIG_UPLOAD_DSYM_ONLY="$7"
            
            # 检查必须参数是否设置
            if [ ! "${CONFIG_BUGLY_APP_ID}" ]; then
            exitWithMessage "Error: Bugly App ID not defined. Please set 'BUGLY_APP_ID' " 0
            fi
            
            if [[ "${CONFIG_BUGLY_APP_ID}" == *"App ID"* ]]; then
            exitWithMessage "Error: Bugly App ID not defined." 0
            fi
            
            if [ ! "${CONFIG_BUGLY_APP_KEY}" ]; then
            exitWithMessage "Error: Bugly App Key not defined." 0
            fi
            
            if [ ! "${CONFIG_BUGLY_APP_BUNDLE_IDENTIFIER}" ]; then
            exitWithMessage "Error: Bundle Identifier not defined." 0
            fi
            
            if [ ! "${CONFIG_BUGLY_APP_VERSION}" ]; then
            exitWithMessage "Error: App Version not defined." 0
            fi
            
            if [ ! -e "${CONFIG_DSYM_SOURCE_DIR}" ]; then
            exitWithMessage "Error: Invalid dir ${CONFIG_DSYM_SOURCE_DIR}" 0
            fi
            
            if [ ! "${CONFIG_DSYM_DEST_DIR}" ]; then
            exitWithMessage "Error: Invalid dir ${CONFIG_DSYM_DEST_DIR}" 0
            fi
            
            if [ ! -e "${CONFIG_DSYM_DEST_DIR}" ]; then
            mkdir ${CONFIG_DSYM_DEST_DIR}
            fi
            
            DSYM_FOLDER="${CONFIG_DSYM_SOURCE_DIR}"
            IFS=$'\n'
            
            echo "Scaning dSYM FOLDER: ${DSYM_FOLDER} ..."
            RET="F"
            
            #
            for dsymFile in $(find "$DSYM_FOLDER" -name '*.dSYM'); do
            RET="T"
            echo "Found dSYM file: $dsymFile"
            
            DSYM_FILE_NAME=${dsymFile##*/}
                DSYM_SYMBOL_ZIP_FILE_NAME="${DSYM_FILE_NAME}.zip"
                DSYM_SYMBOL_ZIP_FILE_NAME="${DSYM_SYMBOL_ZIP_FILE_NAME// /_}"
                DSYM_SYMBOL_ZIP_FILE=${CONFIG_DSYM_DEST_DIR}/${DSYM_SYMBOL_ZIP_FILE_NAME}
                
                if [ $CONFIG_UPLOAD_DSYM_ONLY -eq 1 ]; then
                if [ -e $DSYM_SYMBOL_ZIP_FILE ]; then
                rm -f $DSYM_SYMBOL_ZIP_FILE
                fi
                # 如果只上传dSYM,直接压缩dSYM目录
                zip -r -j $DSYM_SYMBOL_ZIP_FILE $dsymFile -x *.plist
                else
                # 使用符号表工具来生成Symbol文件
                dSYMParse $dsymFile $DSYM_SYMBOL_ZIP_FILE
                fi
                
                # 上传
                dSYMUpload $CONFIG_BUGLY_APP_ID $CONFIG_BUGLY_APP_KEY $CONFIG_BUGLY_APP_BUNDLE_IDENTIFIER $CONFIG_BUGLY_APP_VERSION $DSYM_SYMBOL_ZIP_FILE
                done
                
                if [ $RET = "F" ]; then
                exitWithMessage "No .dSYM found in ${DSYM_FOLDER}" 0
                fi
            }
            
            # 在Xcode工程中执行
            function runInXcode(){
                echo "Uploading dSYM to Bugly in Xcode ..."
                
                echo "Info.Plist : ${INFOPLIST_FILE}"
                
                BUNDLE_VERSION=$(/usr/libexec/PlistBuddy -c 'Print CFBundleVersion' "${INFOPLIST_FILE}")
                BUNDLE_SHORT_VERSION=$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "${INFOPLIST_FILE}")
                
                # 组装Bugly默认识别的版本信息(格式为CFBundleShortVersionString(CFBundleVersion), 例如: 1.0(1))
                if [ ! "${CUSTOMIZED_APP_VERSION}" ]; then
                BUGLY_APP_VERSION="${BUNDLE_SHORT_VERSION}(${BUNDLE_VERSION})"
                else
                BUGLY_APP_VERSION="${CUSTOMIZED_APP_VERSION}"
                fi
                
                echo "--------------------------------"
                echo "Prepare application information."
                echo "--------------------------------"
                
                echo "Product Name: ${PRODUCT_NAME}"
                echo "Bundle Identifier: ${BUNDLE_IDENTIFIER}"
                echo "Version: ${BUNDLE_SHORT_VERSION}"
                echo "Build: ${BUNDLE_VERSION}"
                
                echo "Bugly App ID: ${BUGLY_APP_ID}"
                echo "Bugly App key: ${BUGLY_APP_KEY}"
                echo "Bugly App Version: ${BUGLY_APP_VERSION}"
                
                echo "--------------------------------"
                echo "Check the arguments ..."
                
                ##检查模拟器编译是否允许上传符号
                if [ "$EFFECTIVE_PLATFORM_NAME" == "-iphonesimulator" ]; then
                if [ $UPLOAD_SIMULATOR_SYMBOLS -eq 0 ]; then
                exitWithMessage "Warning: Build for simulator and skipping to upload. \nYou can modify 'UPLOAD_SIMULATOR_SYMBOLS' to 1 in the script." 0
                fi
                fi
                
                ##检查是否是Release模式编译
                if [ "${CONFIGURATION=}" == "Debug" ]; then
                if [ $UPLOAD_DEBUG_SYMBOLS -eq 0 ]; then
                exitWithMessage "Warning: Build for debug mode and skipping to upload. \nYou can modify 'UPLOAD_DEBUG_SYMBOLS' to 1 in the script." 0
                fi
                fi
                
                ##检查是否Archive操作
                if [ $UPLOAD_ARCHIVE_ONLY -eq 1 ]; then
                if [[ "$TARGET_BUILD_DIR" == *"/Archive"* ]]; then
                echo "Archive the package"
                else
                exitWithMessage "Warning: Build for NOT Archive mode and skipping to upload. \nYou can modify 'UPLOAD_ARCHIVE_ONLY' to 0 in the script." 0
                fi
                fi
                
                #
                run ${BUGLY_APP_ID} ${BUGLY_APP_KEY} ${BUNDLE_IDENTIFIER} ${BUGLY_APP_VERSION} ${DWARF_DSYM_FOLDER_PATH} ${BUILD_DIR}/BuglySymbolTemp ${UPLOAD_DSYM_ONLY}
            }
            
            # 根据Xcode的环境变量判断是否处于Xcode环境
            INFO_PLIST_FILE="${INFOPLIST_FILE}"
            
            BuildInXcode="F"
            if [ -f "${INFO_PLIST_FILE}" ]; then
            BuildInXcode="T"
            fi
            
            if [ $BuildInXcode = "T" ]; then
            runInXcode
            else
            echo "\nUsage: dSYMUpload.sh <bugly_app_id> <bugly_app_key> <app_bundle_identifier> <app_version> <dSYM_src_dir> <bSYMBOL_dest_dir> [upload_dsym_only]\n"
            # 你可以在此处直接设置BuglyAppID和BuglyAppKey,排除不常变参数的输入
            BUGLY_APP_ID="$1"
            BUGLY_APP_KEY="$2"
            BUNDLE_IDENTIFIER="$3"
            BUGLY_APP_VERSION="$4"
            DWARF_DSYM_FOLDER_PATH="$5"
            SYMBOL_OUTPUT_PATH="$6"
            UPLOAD_DSYM_ONLY=$7
            run ${BUGLY_APP_ID} ${BUGLY_APP_KEY} ${BUNDLE_IDENTIFIER} ${BUGLY_APP_VERSION} ${DWARF_DSYM_FOLDER_PATH} ${SYMBOL_OUTPUT_PATH} ${UPLOAD_DSYM_ONLY}
            fi

ERROR ITMS-90087解决脚本

APP_PATH="${TARGET_BUILD_DIR}/${WRAPPER_NAME}"

# This script loops through the frameworks embedded in the application and
# removes unused architectures.
find "$APP_PATH" -name '*.framework' -type d | while read -r FRAMEWORK
do
FRAMEWORK_EXECUTABLE_NAME=$(defaults read "$FRAMEWORK/Info.plist" CFBundleExecutable)
FRAMEWORK_EXECUTABLE_PATH="$FRAMEWORK/$FRAMEWORK_EXECUTABLE_NAME"
echo "Executable is $FRAMEWORK_EXECUTABLE_PATH"

EXTRACTED_ARCHS=()

for ARCH in $ARCHS
do
echo "Extracting $ARCH from $FRAMEWORK_EXECUTABLE_NAME"
lipo -extract "$ARCH" "$FRAMEWORK_EXECUTABLE_PATH" -o "$FRAMEWORK_EXECUTABLE_PATH-$ARCH"
EXTRACTED_ARCHS+=("$FRAMEWORK_EXECUTABLE_PATH-$ARCH")
done

echo "Merging extracted architectures: ${ARCHS}"
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"
rm "${EXTRACTED_ARCHS[@]}"

echo "Replacing original executable with thinned version"
rm "$FRAMEWORK_EXECUTABLE_PATH"
mv "$FRAMEWORK_EXECUTABLE_PATH-merged" "$FRAMEWORK_EXECUTABLE_PATH"

done

找到view所在controller

func responderViewController() -> UIViewController { 
  var responder: UIResponder! = nil 
  var next = self.superview 
  while next != nil { 
    responder = next?.next 
    if (responder!.isKind(of: UIViewController.self)){ 
      return (responder as! UIViewController) 
    } 
    next = next?.superview 
  } 
  return (responder as! UIViewController)
}

强制转屏

override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
   
UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
UIApplication.shared.statusBarOrientation = .landscapeRight
}

判断系统SDK是否支持iOS10

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0
#import <UserNotifications/UserNotifications.h>
@interface AppDelegate() <UNUserNotificationCenterDelegate>
@end
#endif

判断设备为手机还是iPad

if UI_USER_INTERFACE_IDIOM() == .Phone {
}
/*
    case Unspecified
    @available(iOS 3.2, *)
    case Phone // iPhone and iPod touch style UI
    @available(iOS 3.2, *)
    case Pad // iPad style UI
    @available(iOS 9.0, *)
    case TV // Apple TV style UI
    @available(iOS 9.0, *)
    case CarPlay // CarPlay style UI
*/

UISegmentedControl样式自定义

let sc: UISegmentedControl = UISegmentedControl(items: ["111","222"])
sc.layer.borderWidth = 0
        sc.layer.borderColor = UIColor.clearColor().CGColor
        sc.layer.cornerRadius = 0
        sc.selectedSegmentIndex = 0
        sc.setTitle("问答讨论", forSegmentAtIndex: 0)
        sc.setTitle("学习资料", forSegmentAtIndex: 1)
        
        sc.setBackgroundImage(UIImage(), forState: .Normal, barMetrics: .Default)
        sc.setBackgroundImage(UIImage(), forState: .Selected, barMetrics: .Default)
        sc.setBackgroundImage(UIImage(), forState: .Highlighted, barMetrics: .Default)

        sc.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor(hex: 0xa8a8a8)!, NSFontAttributeName: UIFont.systemFontOfSize(16)], forState: .Normal)
        sc.setTitleTextAttributes([NSForegroundColorAttributeName: CommenColor.Blue, NSFontAttributeName: UIFont.systemFontOfSize(16)], forState: .Selected)
        sc.setDividerImage(UIImage(named: "line_vertical"), forLeftSegmentState: .Normal, rightSegmentState: .Normal, barMetrics: .Default)

Swift懒加载

private lazy var tableView: UITableView = {
        let tb: UITableView = UITableView(frame: CGRectZero, style: .Plain)
        tb.delegate = self
        tb.dataSource = self
        
        tb.registerClass(LiveChatViewCell.self, forCellReuseIdentifier: "LiveChatViewCell")
        
        tb.separatorStyle = .None
        tb.tableFooterView = UIView()

        tb.estimatedRowHeight = 60
        tb.rowHeight = UITableViewAutomaticDimension
        return tb
    }()

UIView布局动画

// 告诉self.view约束需要更新
view.needsUpdateConstraints()
 // 调用此方法告诉self.view检测是否需要更新约束,若需要则更新,下面添加动画效果才起作用
view.updateConstraintsIfNeeded()
// 更新动画
UIView.animateWithDuration(duration) { 
    self.view.layoutIfNeeded()
}

UILabel富文本

let attach = NSTextAttachment()
attach.image = UIImage(named: "icon")
attach.bounds = CGRectMake(0, -2, 32, 18)
let attr = NSMutableAttributedString(string: "  标题")
attr.insertAttributedString(NSAttributedString(attachment: attach), atIndex: 0)

UILabel/UITextView加载html文本

NSString * htmlString = @"<html><body>xxxx</body></html>";  
NSAttributedString * attrStr = [[NSAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUnicodeStringEncoding] 
options:@{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType } documentAttributes:nil error:nil];  
UILabel * myLabel = [[UILabel alloc] initWithFrame:self.view.bounds];  
myLabel.attributedText = attrStr;   

准确实时判断UIPanGesture的滑动方向

方法1:垂直/水平方向

        // 根据上次和本次移动的位置,算出一个速率的point
        let velocityPoint = pan.velocity(in: self)

        switch pan.state {
        case UIGestureRecognizerState.began:
            // 使用绝对值来判断移动的方向
            
            let x = fabs(velocityPoint.x)
            let y = fabs(velocityPoint.y)
            
            if x > y {
                self.panDirection = .horizontal
            } else {
                self.panDirection =.vertical
            }

方法2:向左/右

//滑动手势方法
-(void)actionOfPan: (UIPanGestureRecognizer*)pan
{
    CGFloat location = [pan locationInView:pan.view];
    static CGFloat lastX = 0; //静态局部变量,记录上一次位置
    switch (pan.state)
    {
        case UIGestureRecognizerStateBegan:
        {
            lastX = location.x; //开始时上一次滑动位置
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            CGFloat delta = location.x - lastX; //计算差值
            if (delta == 0) {
                return;
            }
            if (delta < 0) {
                NSLog(@"<----"); //向左
            }
            else{
                NSLog(@"---->"); //向右
            }
            lastX = location.x; //滑动时重新赋值上一次记录
        }
            break;
        case UIGestureRecognizerStateEnded:
        {
            lastX = 0; //重置
        }
            break;
        default:
        {
        }
            break;
    }
}

NSString中文编码

NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test.txt" ofType:nil]];
NSStringEncoding encode = CFStringConvertEncodingToNSStringEncoding (kCFStringEncodingGB_18030_2000);
NSString *str = [[NSString alloc] initWithData:data encoding:encode];

点击缩放跳转动画

// point缩放动画
-(void)pagePositionScaleAnimationWithCell:(UICollectionViewCell*)cell view:(UIView*)view{
    view.center = cell.center;
    view.transform = CGAffineTransformMakeScale(cell.frame.size.width/view.frame.size.width, cell.frame.size.height/view.frame.size.height);

    CGFloat x = cell.center.x;
    CGFloat y = cell.frame.origin.y - self.collectionView.contentOffset.y + cell.frame.size.height/2 + 64;
    view.center = CGPointMake(x, y);
//    view.alpha = 0;
    
    [UIView animateWithDuration:0.3 animations:^{
        view.transform = CGAffineTransformMakeScale(1, 1);
        view.center = self.view.center;
//        view.alpha = 1;
    }];
}

// 水波动画
-(void)pageRippleAnimation:(UIView*)view{
    CATransition *transition = [CATransition animation];
    transition.duration = 0.8;
    transition.type = @"rippleEffect";
    // rippleEffect | cube | pageCurl | pageUnCurl | suckEffect | oglFlip | moveIn | fade | reveal
    [view.layer addAnimation:transition forKey:nil];
}

// center缩放动画
-(void)pageScaleAnimation:(UIView*)view{
    view.frame = self.view.frame;
    view.transform = CGAffineTransformMakeScale(0.1, 0.1);
    view.alpha = 0;
    
    [UIView animateWithDuration:0.3 animations:^{
        view.transform = CGAffineTransformMakeScale(1, 1);
        view.alpha = 1;
    }];
}

KVO实现数组个数改变的监听

//1. 定义数组
@property(nonatomic,strong)NSMutableArray *array;

//2. add observer --- @count为数组的属性
[self addObserver:self forKeyPath:@"array.@count" 
options:NSKeyValueObservingOptionNew context:nil];

//3. 改变数组的值,但注意不能用[array addObject: ] 或 [array insert]、removeObject等去改变数组的元素
//而是要改用:
[[self mutableArrayValueForKey:@"array"] addObject: xxx]; // KVC的方式获取数组

//4. 监听处理
-(void)observeValueForKeyPath:(NSString *)keyPath 
ofObject:(id)object change:(NSDictionary<NSString *,id> *)change 
context:(void *)context{
    if ([keyPath isEqualToString:@"pathArray.@count"]) {
        NSInteger count = [change[NSKeyValueChangeNewKey] integerValue];
        // do something...
    }
}

图片裁剪

// 从view渲染得到图片
-(UIImage *)renderImageFromCurrentView{
    // 渲染
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return image;
}
// 裁剪 - !!!rect记得 x 缩放比
-(UIImage *)clipImageFromOriginalImage: (UIImage*)orgImage
                                inRect: (CGRect)rect{

    rect.origin.x *= orgImage.scale;
    rect.origin.y *= orgImage.scale;
    rect.size.width *= orgImage.scale;
    rect.size.height *= orgImage.scale;
    
    UIImage *image = [UIImage imageWithCGImage:CGImageCreateWithImageInRect(orgImage.CGImage, rect)];
    return image;
}

计算出绘图轮廓矩形

-(CGRect)getOutlineRectOfCurrentPaths{ //轮廓矩形
    CGFloat xmin = CGRectGetMaxX(self.bounds);
    CGFloat ymin = CGRectGetMaxY(self.bounds);
    CGFloat xmax = 0;
    CGFloat ymax = 0;
    
    for (UIBezierPath *path in self.pathArray)
    {
        NSMutableArray *points = [NSMutableArray array];
        CGPathApply(path.CGPath, (__bridge void *)points, getPointsFromBezier);
        
        for (int i=0; i<points.count; i++)
        {
            CGFloat x = [points[i] CGPointValue].x;
            CGFloat y = [points[i] CGPointValue].y;
            if (x < xmin) {
                xmin = x;
            }
            if (x > xmax) {
                xmax = x;
            }
            if (y < ymin) {
                ymin = y;
            }
            if (y > ymax) {
                ymax = y;
            }
        }
    }
    
    CGRect rect = CGRectMake(xmin, ymin, xmax-xmin, ymax-ymin);
    return rect;
}

获取UIBezierPath上的所有点

// 用法:
NSMutableArray *keyPoints = [NSMutableArray array];
CGPathApply(yourPath.CGPath, (__bridge void *)keyPoints, getPointsFromBezier);

// 现成方法
void getPointsFromBezier (void *info, const CGPathElement *element) {
    NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;
    
    CGPoint *points = element->points;
    CGPathElementType type = element->type;
    
    switch(type) {
        case kCGPathElementMoveToPoint: // contains 1 point
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
            break;
            
        case kCGPathElementAddLineToPoint: // contains 1 point
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
            break;
            
        case kCGPathElementAddQuadCurveToPoint: // contains 2 points
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
            break;
            
        case kCGPathElementAddCurveToPoint: // contains 3 points
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[0]]];
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[1]]];
            [bezierPoints addObject:[NSValue valueWithCGPoint:points[2]]];
            break;
            
        case kCGPathElementCloseSubpath: // contains no point
            break;
    }
}

半透明present出来的viewcontroller

MyViewController *vc = [MyViewController new];
vc.view.backgroundColor = [UIColor clearColor];
vc.modalPresentationStyle = UIModalPresentationOverCurrentContext; 
//尝试了这种模式下切换比较自然且会起到作用

隐藏tableview多余的分割线

//1 以前我是将left的margin设置得很大,目的是‘挤跑’多余的分割线
        tableView.separatorInset = UIEdgeInsetsMake(0, 100000, 0, 0);
// 然后在cell中,又设置回来,比如
        cell.separatorInset = UIEdgeInsetsMake(0, 10, 0, 0);

//2 后来发现有人这样做:【推荐】
        tableView.tableFooterView = [[UIView alloc] initWithFrame:CGRectZero];

数组元素排序,NSString比较

需求:顺序/倒序 排序数组中的name属性

NSArray *array = @[
  @{
   @"name": @"02. 资源1"
},
@{
   @"name": @"03-1. 资源2"
},
@{
   @"name": @"02-1. 资源3"
}
];
-(NSArray*)sortedArrayWithArray: (NSArray*)array{
    NSComparator sort = ^(NSDictionary *obj1, NSDictionary *obj2){
        NSRange range = NSMakeRange(0, 6); //03-00 < 03-01
        // 顺序
        return [obj1[@"name"] compare:obj2[@"name"] options:NSNumericSearch range:range];
        // 倒序
//        return [obj2[@"name"] compare:obj1[@"name"] options:NSNumericSearch range:range];
    };
    NSArray *result = [array sortedArrayUsingComparator:sort];
    NSLog(@"字符串数组排序结果%@",result);
    return result;
}

输出:[
  @{
   @"name": @"02. 资源1"
},
@{
   @"name": @"02-1. 资源2"
},
@{
   @"name": @"03-1. 资源3"
}
];

NSString-boundingRect-较为精确

//包含换行、回车、空格等都会计算高度
NSDictionary *attributes = @{NSFontAttributeName: [UIFont systemFontOfSize:16]};
CGSize size = [str boundingRectWithSize:CGSizeMake(width, 0) 
options: NSStringDrawingTruncatesLastVisibleLine | 
NSStringDrawingUsesLineFragmentOrigin | 
NSStringDrawingUsesFontLeading 
attributes:attributes context:nil].size;

swift将图片重新渲染成新的颜色

extension UIView类的类方法

class func renderWithNewColor(imageName:String, color: UIColor) -> UIImage{
        let image = UIImage(named: imageName)!
        // @2x的图建议*2 不然会虚  一般的.png图就不用*1
        let size = CGSizeMake(image.size.width * 2, image.size.height * 2)
        
        UIGraphicsBeginImageContext(size)
        let context = UIGraphicsGetCurrentContext();
        CGContextTranslateCTM(context, 0, size.height);
        CGContextScaleCTM(context, 1.0, -1.0);
        CGContextSetBlendMode(context, .Normal);
        let rect = CGRectMake(0, 0, size.width, size.height);
        CGContextClipToMask(context, rect, image.CGImage);
        color.setFill()
        CGContextFillRect(context, rect);
        let newImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        return newImage;
    }

OC将UIView转UIImage

-(UIImage*)viewRenderToImage: (UIView*)view{
    CGSize size = view.bounds.size;
//第一个参数表示区域大小。第二个参数表示是否是非透明的。如果需要显示半透明效果,需要传NO,否则传YES。第三个参数就是屏幕密度了
    UIGraphicsBeginImageContextWithOptions(size, false, [UIScreen mainScreen].scale);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return image;
}

OC单例

+(Manager*)sharedManager{
    static Manager* manager = nil;
    static dispatch_once_t token;
    dispatch_once(&token, ^{
        manager = [Manager new];
    });
    return manager;
}

屏幕旋转 - 切记项目支持全屏时,该方法才起作用

配置.png
-(UIInterfaceOrientationMask)supportedInterfaceOrientations{
    return UIInterfaceOrientationMaskPortrait;
}

-(BOOL)shouldAutorotate{
    return true;
}

-(UIInterfaceOrientation)preferredInterfaceOrientationForPresentation{
    return UIInterfaceOrientationPortrait;
}

选中/取消选中动画

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}

cell复用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    
    [self configureCell:cell forIndexPath:indexPath];
    
    return cell;
}

swift协议

@objc
protocol XXXDelegate :NSObjectProtocol {
    // 方法
    optional func xxx()
}
// 代理
    weak var delegate : XXXDelegate?

NavBar全透明

 nav.navigationBar.setBackgroundImage(UIImage(),forBarMetrics: UIBarMetrics.Default)
 nav.navigationBar.shadowImage = UIImage()
 nav.navigationBar.translucent = true

TabBar

// 添加子控件
    private func addViewController(childController: UIViewController, title: String) {
        let nav = NavgationViewController(rootViewController: childController)
        addChildViewController(nav)
        childController.tabBarItem.title = title
        childController.tabBarItem.image = UIImage(named: "tb_\(childViewControllers.count - 1)")
        childController.tabBarItem.selectedImage = UIImage(named: "tb_\(childViewControllers.count - 1)" + "_selected")
        // 设置tabBarItem的tag, 方便判断点击
        childController.tabBarItem.tag = childViewControllers.count-1
    }
    

// 点击profile的时候.判断是否登录. 如果没有登录, 需要跳转到登录界面, 反之则跳转到个人界面
    func tabBarController(tabBarController: UITabBarController, shouldSelectViewController viewController: UIViewController) -> Bool
    {
        return true
    }

swift-单例

// 一句话实现单例(swift2.0)
static let sharedInstance = LoginHelper()
private override init() {} // 防止使用()初始化

swift-UIView自定义初始化方法

    convenience init(iconName: String, placeHolder: String, isLocation: Bool, isPhone: Bool, isSafe: Bool)
    {
        self.init(frame: CGRectZero)
        // 其他初始化设置
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容