xcrun: error: unable to find utility "xcodebuild", not a developer tool or in PATH
Workaround for Cocoapods issue #7606
post_install do |installer|
installer.pods_project.build_configurations.each do |config|
config.build_settings.delete('CODE_SIGNING_ALLOWED')
config.build_settings.delete('CODE_SIGNING_REQUIRED')
end
end
关于NSArray+NSDictionary数据筛选逻辑
// 有如下一个array
NSArray *tempList = @[
@{
@"id1": 1,
@"name1": @"xxx",
@"list1": @[
@{
@"id2": 2,
@"name2": @"yyy",
@"list2": @[
@{
@"id3": 3
}
]
}
]
},
@{
@"id1": 1,
@"name1": @"xxx",
@"list1": @[
@{
@"id2": 2,
@"name2": @"yyy",
@"list2": @[
@{
@"id3": 3
},
@{
@"id3": 3
}
]
}
]
}
];
// 然后要求移除子list为空的元素
// 真正要执行remove操作的对象
NSMutableArray *list = [tempList mutableCopy];
for (id tempObj1 in tempList) {
// 真正更新的obj1
NSMutableDictionary *obj1 = [tempObj1 mutableCopy];
NSArray *tempList1 = tempObj1[@"list1"];
// 真正要执行remove操作的对象
NSMutableArray *list1 = [tempList1 mutableCopy];
for (id obj2 in tempList1) {
//...类似上面步骤
}
// 数据更新,如果为空,则移除
if (list1.count == 0) {
// 由于list和tempList是copy方式,所以内含的object也是copy
[list removeObject:tempObj1];
}
else{
// 更新list
obj1[@"list1"] = list;
list[[list indexOfObject:obj1]];
}
}
iPhone全面屏适配问题
// 底部布局边距
CGFloat layoutBottom = -20;
// iPhoneX是iOS11以后出现的(不包括越狱情况)
if (@available(iOS 11.0, *)) {
CGFloat bottom = UIApplication.sharedApplication.delegate.window.safeAreaInsets.bottom;
// iPhoneX等
if (bottom > 0) {
layoutBottom = -bottom;
}
}
[self.submitBtn mas_makeConstraints:^(MASConstraintMaker *make) {
make.bottom.mas_equalTo(layoutBottom);
}];
关于pod库如果只支持真机绕开pod lib lint的配置
- 命令行
gem which cocoapods
- 命令行
open /Library/Ruby/Gems/2.3.0/gems/cocoapods-1.5.3/lib/cocoapods/validator.rb
- 将
when :ios
相关改为command += %w(--help)
关于多级页面跳转
注意
popToRoot
和pushNewVC
的先后顺序,否则会出现内存泄漏问题
[[self.goMyOrderBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self);
// 选中我的页面
UINavigationController *nav = weakTabBarVC.viewControllers.lastObject;
[weakTabBarVC setSelectedViewController:nav];
// 跳转到订单页面
UIViewController *vc = [ZSMyOrderModuler getMyOrderViewController];
vc.hidesBottomBarWhenPushed = YES;
[nav pushViewController:vc animated:NO];
// 返回首页
[self.navigationController popToRootViewControllerAnimated:NO];
}];
关于tabbar选中某个页面
// 尽量用这种方式而不是setSelectedIndex
UINavigationController *myCourseNav = [ZSApp.rootTabBarController.viewControllers objectAtIndex:1];
[ZSApp.rootTabBarController setSelectedViewController:myCourseNav];
-ObjC
这个flag告诉链接器把库中定义的Objective-C类和Category都加载进来。这样编译之后的app会变大(因为加载了其他的objc代码进来)。但是如果静态库中有类和category的话只有加入这个flag才行。
-all_load
这个flag是专门处理-ObjC的一个bug的。用了-ObjC以后,如果类库中只有category没有类的时候这些category还是加载不进来。变通方法就是加入-all_load或者-force-load。-all_load会强制链接器把目标文件都加载进来,即使没有objc代码。-force_load在xcode3.2后可用。但是-force_load后面必须跟一个只想静态库的路径。
全局隐藏导航栏返回按钮文字
[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(NSIntegerMin, 0) forBarMetrics:UIBarMetricsDefault];
关于导航栏的正确隐藏
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
关于Xcode10断点不执行/执行混乱问题
关于tableView: heightForHeader/FooterInSection:
注意
- 即使你返回了
numberOfSections
为0,如果你设置height为0,则height会变成默认的38,造成tableView的contentSize为2x38!=0,如果你监听这个contentSize则会出现问题。 - 所以你的return 0.00001才行。
- 然后监听contentSize.height需大于1才行。
关于RACObserve(self.array, count)监听不起作用问题
- 使用
mutableArrayValueForKey
才能行
[[self mutableArrayValueForKey:@"array"] addObject:@"1"]
关于OC中对象地址和对象指针地址
打印对象地址: NSLog(@"%p", obj);
打印对象指针地址: NSLog(@"%x", obj);
判断两个对象地址相等: obj1 == obj2即可
判断字符串相等: [str1 isEqualToString: str2]
注意[obj1 isEqual: obj2]只是比较hash值,不是内存地址
关于Cell上的按钮rac_signalForControlEvents:UIControlEventTouchUpInside
方法多次调用问题
// 这种方法最靠谱
[cell.btn addTarget:self action:@selector(btn_clicked:) forControlEvents:UIControlEventTouchUpInside];
// 还有一种说是这样,但亲测并不起作用
[[[cell.btn rac_signalForControlEvents:UIControlEventTouchUpInside] takeUntil:cell.rac_prepareForReuseSignal] subscribeNext:^(__kindof UIControl * _Nullable x) {
}];
iOS9及以下UIBarButtonItem设置问题
// 如果为customView时,则需指定customView的frame,否则显示不出来
- (UIBarButtonItem *)titleBtnItem{
if (!_titleBtnItem) {
UILabel *label = [UILabel new];
label.font = ZSAppFont.system17.bold;
label.text = @"学习内容";
[label sizeToFit]; // 注意此处
_titleBtnItem = [[UIBarButtonItem alloc] initWithCustomView:label];
}
return _titleBtnItem;
}
关于CocoaPods中bitcode设置问题:
- 如果让当前pod库bitcode为NO,则在.podspec文件中设置如下:
s.pod_target_xcconfig = { 'ENABLE_BITCODE' => 'NO' }
- 如果使工程中的所有pod库bitcode都为NO,则在Podfile文件中设置如下:
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
UIProgressView的两种样式
UIProgressViewStyleBar //没有圆角
UIProgressViewStyleDefault //有圆角
// 如果改变圆角大小,可通过以下方式
for (UIImageView *iv in _progressView.subviews) {
iv.layer.masksToBounds = YES;
iv.layer.cornerRadius = 10;
}
关于super view不可用而某个sub view可用的逻辑
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event{
UIView *hitTestView = [super hitTest:point withEvent:event];
if ([self pointInside:point withEvent:event]) {
CGPoint newPoint = [self convertPoint:point toView:self.rightView];
BOOL rightViewEnabled = CGRectContainsPoint(self.rightView.bounds, newPoint) && self.rightView.userInteractionEnabled;
return rightViewEnabled
? self.rightView
: (self.enabled && self.userInteractionEnabled && self.alpha > 0
? self
: hitTestView);
}
return hitTestView;
}
Category交换系统dealloc方法
#import "UIView+Dealloc.h"
#import <objc/runtime.h>
@implementation UIView (Dealloc)
+ (void)load{
Method systemDealloc = class_getInstanceMethod(self, NSSelectorFromString(@"dealloc"));
Method selfDealloc = class_getInstanceMethod(self, @selector(selfDealloc));
method_exchangeImplementations(systemDealloc, selfDealloc);
}
- (void)selfDealloc{
NSLog(@"%@ [deallocated]", self);
[self selfDealloc];
}
@end
关于RAC下如何触发UIButton的点击事件
- 定义按钮事件处理逻辑
@weakify(self);
self.btn.rac_command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
@strongify(self);
// 事件处理...
[subscriber sendCompleted];
return nil;
}];
}];
- 点击事件监听
注意这个地方要防止循环引用
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *x) {
// x不能再重复使用,否则会循环应用
// 应该用[weakSelf.btn.rac_command execute:x]
// ❌
[x.rac_command execute:x];
// ✅
[weakSelf.btn.rac_command execute:x];
}];
- 触发事件
[self.btn.rac_command execute: nil];
libc++abi.dylib`__cxa_throw:
Masonry警告调试
-
添加断点
-
捕获约束警告
- 代码中设置mas_key
// 按make_layout的顺序
MASAttachKeys(self.attachBtn, self.lineView, self.teacherLabel, self.downloadBtn);
// 或者
self.attachBtn.mas_key = @"xxx";
获取UIPageViewController当前页面的index
// delegate方法
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray<UIViewController *> *)previousViewControllers transitionCompleted:(BOOL)completed{
// 单页展示
UIViewController *currentVC = pageViewController.viewControllers.firstObject;
self.selectedPageIndex = [self.pageViewControllers indexOfObject:currentVC];
}
关于UITableViewheightForFooter/Header
代理方法不执行问题
这种情况下,一般是因为只设置了
heightForFooter/Header
方法,而没有设置viewForFooter/Header
。
iOS打开设置页面
- iOS 10之前
`prefs:root=WIFI`
// 或者
`UIApplicationOpenSettingsURLString`
- iOS 10之后
`App-Prefs:root=WIFI`
- 各个设置对应shemes
无线局域网 App-Prefs:root=WIFI
蓝牙 App-Prefs:root=Bluetooth
蜂窝移动网络 App-Prefs:root=MOBILE_DATA_SETTINGS_ID
个人热点 App-Prefs:root=INTERNET_TETHERING
运营商 App-Prefs:root=Carrier
通知 App-Prefs:root=NOTIFICATIONS_ID
通用 App-Prefs:root=General
通用-关于本机 App-Prefs:root=General&path=About
通用-键盘 App-Prefs:root=General&path=Keyboard
通用-辅助功能 App-Prefs:root=General&path=ACCESSIBILITY
通用-语言与地区 App-Prefs:root=General&path=INTERNATIONAL
通用-还原 App-Prefs:root=Reset
墙纸 App-Prefs:root=Wallpaper
Siri App-Prefs:root=SIRI
隐私 App-Prefs:root=Privacy
Safari App-Prefs:root=SAFARI
音乐 App-Prefs:root=MUSIC
音乐-均衡器 App-Prefs:root=MUSIC&path=com.apple.Music:EQ
照片与相机 App-Prefs:root=Photos
FaceTime App-Prefs:root=FACETIME
iPhoneX判断
#define kIsIphoneX CGSizeEqualToSize(CGSizeMake(1125, 2436), UIScreen.mainScreen.currentMode.size)
UIAlertController循环引用问题
一般来说,如果在UIAlertAction的handlerBlock中如果调用了alert对象,就会产生循环引用,解决方法如下:
UIAlertController *alert = [UIAlertController alertxxx];
__weak typeof(UIAlertController *)weakAlert = alert;
// 然后使用weakAlert即可
另一种方法:
__wak __block UIAlertController *alert = nil; // __block可以在handlerBlock中保留该局部变量
// 做一些配置
alert = [UIAlertController showXXX]; // 这里是自定义的展示方法,展示完毕返回一个alert对象
Pods库头文件不提示问题
在工程配置中User Header Search Paths
添加$(PODS_ROOT)
选择recursive
正确获取UIApplication的当前显示viewController
向UIApplication添加Category方法:
+ (UIViewController *)currentViewController{
UIViewController *vc = self.keyWindow.rootViewController;
while (1) {
if ([vc isKindOfClass:UITabBarController.class]) {
vc = ((UITabBarController *)vc).selectedViewController;
}
else if ([vc isKindOfClass:UINavigationController.class]) {
vc = ((UINavigationController *)vc).visibleViewController;
}
else if (vc.presentedViewController) {
vc = vc.presentedViewController;
}
else{
break;
}
}
return vc;
}
This app could not be installed at this time.解决方法
打开
~/Library/Logs/CoreSimulator/CoreSimulator.log
日志,如果提示did not have a CFBundleIdentifier in its Info.plist
,则删除~/Library/Developer/Xcode/DerivedData/
文件夹重新编译运行。
UILongPressGesture使用注意
UILongPressGestureRecognizer *gesture = [UILongPressGestureRecognizer new];
[gesture.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
@strongify(self);
// Began、Changed、Ended
NSLog(@"%@",x);
if (x.state == UIGestureRecognizerStateBegan) {
[self.viewModel.saveQRCodeImageSubject sendNext:[UIImage imageNamed:@"qrcode_app"]];
}
}];
UITableViewStyleGrouped样式下header多余空白问题
_tableView.tableHeaderView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, CGFLOAT_MIN)];
如果设置了tableView.delegate,则还要设置footerView为[UIView new],且高度为CGFLOAT_MIN;
Pod lib lint/repo push错误include of non-modular header inside framework module
解决办法:
pod lib lint xxx --use-libraries
即可
或者
在import ""
头文件方式改为import <>
,然后重新编译
iOS国际化
安装CocoaPods报错
执行sudo gem install cocoapods
报错You don't have write permissions for the /usr/bin directory.
解决办法:sudo gem install -n /usr/local/bin cocoapods
CocoaPods
pod install --verbose --no-repo-update
pod update --verbose --no-repo-update
关于UIButton设置频繁setTitle时闪烁问题
其实这时候buttonType应该为custom类型,然后:
btn.titleLabel.text = @"xxx";
[btn setTitle:@"xxx" forState:UIControlStateNormal];
XCode8以后安装插件
https://blog.csdn.net/lincsdnnet/article/details/77412878
UIViewController生命周期
+[load]
-[initWithCoder:]
-[viewDidLoad:]
-[viewWillAppear:]
-[updateViewConstraints] //一般在这里进行自动布局的代码-Masonry/SnapKit
-[viewWillLayoutSubviews] //add/removeSubview操作都会引起多次条用layoutSubviews
-[viewDidLayoutSubviews]
-[viewWillLayoutSubviews]
-[viewDidLayoutSubviews]
-[viewDidAppear:]
-[viewWillDisappear:]
-[viewDidDisappear:]
-[dealloc]
OC Block
@property(nonatomic, copy)void (^属性名)(参数类型);
// 或者定义为类型
typedef 返回值类型(^Block名)(参数类型);
Swift中的available
@available(iOS x, *) //方法、属性
func xxx(){
}
if #available(iOS x, *) { //代码块
}
实现一个有placeholder的textView
import UIKit
class PlaceTextView: UITextView {
// MARK: - IBOutlets
fileprivate lazy var placeholderLabel: UILabel = {
let lb: UILabel = .init(frame: .init(x: 10, y: 10, width: self.bounds.width - 20, height: 20))
lb.font = self.font
lb.textColor = .lightGray
lb.numberOfLines = 1
lb.text = self.placeholder
return lb
}()
fileprivate lazy var clearButton: UIButton = {
let btn: UIButton = UIButton(type: .system)
btn.setTitle("×", for: .normal)
btn.titleLabel?.font = UIFont.systemFont(ofSize: 18)
btn.tintColor = .white
btn.backgroundColor = .gray
btn.isHidden = true
btn.layer.cornerRadius = self.clearButtonSize / 2
btn.addTarget(self, action: #selector(self.clearInput), for: .touchUpInside)
return btn
}()
// MARK: - Properties
fileprivate let clearButtonSize: CGFloat = 20
var placeholder: String? = "请输入..."{
didSet{
placeholderLabel.text = placeholder
}
}
override var textContainerInset: UIEdgeInsets{
didSet{
super.textContainerInset = UIEdgeInsets(top: textContainerInset.top, left: textContainerInset.left, bottom: textContainerInset.bottom, right: textContainerInset.right + clearButtonSize + 10)
}
}
// MARK: - Initial Method
private func setupUI() {
self.textContainerInset = super.textContainerInset
addSubview(clearButton)
addSubview(placeholderLabel)
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange), name: NSNotification.Name.UITextViewTextDidChange, object: nil)
}
// MARK: - Lifecycle Method
override func awakeFromNib() {
super.awakeFromNib()
setupUI()
}
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setupUI()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func layoutSubviews() {
super.layoutSubviews()
layoutClearButton()
layoutPlaceholderLabel()
}
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: - Action & IBOutletAction
@objc func clearInput(){
text = nil
textDidChange()
}
@objc fileprivate func textDidChange(){
let isEmpty = text == nil || text.isEmpty
placeholderLabel.text = isEmpty ? placeholder : nil
clearButton.isHidden = isEmpty
}
// MARK: - Override Method3
// MARK: - Private method
fileprivate func layoutClearButton() {
let x = bounds.width - clearButtonSize - 10
let y = bounds.height / 2 - clearButtonSize / 2 + contentOffset.y
clearButton.frame = CGRect(origin: .init(x: x, y: y), size: CGSize(width: clearButtonSize, height: clearButtonSize))
}
fileprivate func layoutPlaceholderLabel() {
let width = clearButton.frame.origin.x - 20
placeholderLabel.frame = CGRect(origin: .init(x: textContainerInset.left + 4, y: textContainerInset.top), size: CGSize(width: width, height: 14))
}
// MARK: - Public Method
}
统计代码行数
- .swift、.c、.m、.h、.xib等文件
find . -name "*.m" -or -name "*.h" -or -name "*.xib" -or -name "*.c" -or -name "*.swift" |xargs wc -l
带icon的label文本
let attach = NSTextAttachment()
attach.image = UIImage(named: "icon")
attach.bounds = CGRect(origin: CGPoint(x: 0, y: -label.font.pointSize / 2), size: attach.image!.size)
let attr = NSAttributedString(attachment: attach)
let text = NSMutableAttributedString(string: " xxx")
text.insert(attr, at: 0)
label.attributedText = text
判断字符是表情字符
extension String{
var isEmoji: Bool{
return rangeOfCharacter(from: .symbols) != nil
}
}
修改UITextField的clear button
let clearButton = textFiled.value(forKeyPath:"_clearButton")
(clearButton as? UIButton)?.setImage(UIImage(named: "close"), for: .normal)
判断当前输入字符为表情
UITextView/UITextField如果在输入内容改变时.textInputMode?.primaryLanguage == nil
则当前正在输入的是表情字符。
FileManager.default.fileExists
var isDirectory = ObjCBool(true)
FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
关于Swift 字典-模型转换时报错:
[_SwiftTypePreservingNSNumber length]: unrecognized selector sent to instance xxx
- 原因:在将dictionary转换为model过程中,出现了model的属性数据类型和字典值的类型不匹配情况;
- 解决:将model中的属性数据类型改为和dictionary一致;
- 示例:
class Student: NSObject{
var id = 0 //注意是Int类型
var name = ""
}
// dictionary自动填充为model
let student = Student()
student.setValuesForKeys(["id": "0", "name": "xiaoming"])
//注意此处字典中的id数据类型为String,与model的Int不符合,则会出现如上的报错信息;
Swift 函数
lroundf() //四舍五入
ceil() //向上取整
floor() //向下取整
Swift didSet, willSet记
var selectedClass: Class!{
willSet{
print(newValue)
}
didSet{
guard selectedClass != oldValue else {
return
}
requestStudentData()
}
}
Swift reduce
高阶函数用法
let totalProgress = learnings.reduce(0){ $0 + $1.displayProgress }
UITextField输入字符数限制
extension InformationEditVC: UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let cell = textField.superview?.superview as? InformationEditCell {
return string == "" || cell.data.maximumLength == 0 ||
textField.text!.length < cell.data.maximumLength
}
return false
}
}
关于UITableViewController在Storyboard上的Cell复用问题
- 注意这种情况下 不要 用
dequeueReusableCell(withIdentifier: , for:)
指定indexPath方式; - 因为系统创建cell的方式是从xib中init的,然后加入复用队列;
- 如果是自己手动register的cell,则要指定indexPath;
UISearchBar自定制
// 去掉黑边
searchBar.backgroundImage = UIImage()
if let tf = searchBar.value(forKey: "searchField") as? UITextField{
tf.subviews[0].corner(radius: 14) // 输入框圆角
}
关于Swift的array.insert方法注意事项:
- 在Swift中,Array、Dictionary、Set等均为基本数据类型,所以是值拷贝;
- insert方法的执行过程类似于
i += 1
的操作,是在内存中进行的,完成之后将值重新付给array,所以会执行array的didSet方法;
UITableViewCell的xib在其他view上的使用方法
@IBOutlet weak var infoView: UIView!{
didSet{
if let cell = resourceCell {
infoView.addSubview(cell) // 需cell和cell.contentView都添加
infoView.addSubview(cell.contentView)
cell.frame = infoView.bounds
cell.autoresizingMask = [.flexibleWidth, .flexibleHeight]
}
}
}
UIButton设置选中/正常图片的另一种方式
fileprivate lazy var favoriteButton: UIButton = {
let btn = UIButton(type: .system)
btn.setImage(UIImage.named("icon_favorite_24"), for: .normal)
btn.sizeToFit()
btn.addTarget(self, action: #selector(actionToggleFavorite(_:)), for: .touchUpInside)
return btn
}()
@objc fileprivate func actionToggleFavorite(_ sender: UIBarButtonItem) {
data.favorited = !data.favorited
favoriteButton.tintColor = data.favorited ? .appOrange : .white// 改变tintColor即可
}
forEach{}注意
-
.forEach { $0.xxx = xxx }
注意这种情况下,并不能修改$0的属性值; -
.forEach { (item) in item.xxx = xxx }
才能修改属性值;
UISlider注意事项
- 注意
thumbTintColor
和setThumbImage
不可同时设置(只对一种起作用); -
minimumTrackTintColor
和maximumTrackTintColor
和图片的设置原理同上;
关于Storyboard上UITableViewController的设置问题:
- 改变
heightForHeaderInSection
、heightForRowAt
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return section == 0 ? .zero : 10
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return indexPath.section == 0 ? (abs(tableView.contentInset.top) + 110) : 54
}
- 设置BasicCell的icon,并设置tintColor
Swift中关于UIViewController的init方法
- 该方法在从
xib
初始化或无参数的init()
时均会调用
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
计算某个对象的所有后代节点-递归算法
private func descendantNodesOf(ancestor: TreeNode) -> [TreeNode]{
var nodes = [TreeNode]()
nodes.append(contentsOf: ancestor.subNodes)
for node in ancestor.subNodes {
nodes.append(contentsOf: descendantNodesOf(ancestor: node))
}
return nodes
}
App实时帧率计算
- (void)initCADisplayLink {
self.link = [CADisplayLink displayLinkWithTarget:self selector:@selector(tick:)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)tick:(CADisplayLink *)link {
if (self.lastTime == 0) { //对LastTime进行初始化
self.lastTime = link.timestamp;
return;
}
self.count += 1; //记录tick在1秒内执行的次数
NSTimeInterval delta = link.timestamp - self.lastTime; //计算本次刷新和上次更新FPS的时间间隔
//大于等于1秒时,来计算FPS
if (delta >= 1) {
self.lastTime = link.timestamp;
float fps = self.count / delta; // 次数 除以 时间 = FPS (次/秒)
self.count = 0;
[self updateDisplayLabelText: fps];
}
}
UIScrollView在当前有导航栏的控制器里offset偏移问题
self.automaticallyAdjustsScrollViewInsets = false
self.edgesForExtendedLayout = .all
隐藏状态栏
setStatusBarHidden(true, with: .none)
// 或者
app.isStatusBarHidden = true
iOS使用自定义字体
let font = UIFont(name: "xxx")
Swift4中系统通知名称
// Notification.Name.XXX
NotificationCenter.default.addObserver(self, selector: #selector(playerDidReachEnd(_:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
设置view的layer的类型
class ScrollingView: UIView {
override class var layerClass : AnyClass {
return CAScrollLayer.self
}
}
Swift 格式化数字
String(format: "%.1f", slider.value)
Swift - switch-case 用法
- 与where使用
let row = Row(rawValue: indexPath.row)!
switch row {
case .contentsGravity where !contentsGravityPickerVisible:
showContentsGravityPicker()
default:
hideContentsGravityPicker()
}
- 组合用法
@IBAction func scrollingSwitchChanged(_ sender: UISwitch) {
let xOn = horizontalScrollingSwitch.isOn
let yOn = verticalScrollingSwitch.isOn
switch (xOn, yOn) {
case (true, true):
scrollingViewLayer.scrollMode = kCAScrollBoth
case (true, false):
scrollingViewLayer.scrollMode = kCAScrollHorizontally
case (false, true):
scrollingViewLayer.scrollMode = kCAScrollVertically
default:
scrollingViewLayer.scrollMode = kCAScrollNone
}
}
Swift代码标记
// MARK:
// TODO:
// FIXME:
简书上传图片自定义宽度
自定义NSLog宏
#ifdef DEBUG
#define NSLog(FORMAT, ...) fprintf(stderr,"[%s][#%d] - %s\n",
[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],
__LINE__,
[[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#else
#define NSLog(FORMAT, ...) nil
#endif
__VA_ARGS__ 可变参数的宏
__FILE__ 文件全路径
__FUNCTION__ 类/实例方法
__LINE__ 所在行
Xib/Storyboard兼容打开
导航栏动态透明
// 先设置为全透明
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
UINavigationBar *navBar = self.navigationController.navigationBar;
[navBar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
navBar.shadowImage = [UIImage new];
navBar.translucent = true;
}
// 动态透明
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{
CGFloat alpha = scrollView.contentOffset.y / 100;
self.navigationController.navigationBar.backgroundColor = [UIColor.redColor colorWithAlphaComponent:alpha];
}
一种创建对象的代码块
UIView *view = ({
UIView *v = [UIView new];
// do something...
v; // copy `v` to `view`
});
CoreText获取CTLine的bounds方式
- 方法1:
CGFloat a;
CGFloat d;
CGFloat l;
CGFloat width = CTLineGetTypographicBounds(moreLine, &a, &d, &l);
CGFloat height = a + d + l;
CGRect bounds = CGRectMake(0,0,width,height);
- 方法2:
只有当options设置不同时,两种方法计算得到的值才可能不一样
CGRect bounds = CTLineGetBoundsWithOptions(moreLine, kCTLineBoundsUseOpticalBounds)
Git移除变基
rm -rf .git/rebase-apply
UICollectionView空白时无法使用下拉刷新问题
collectionView.alwaysBounceVertical = YES;
禁用导航栏滑动返回
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
Swift利用Mirror获取class或struct的属性和值
func getAllProperties(){
let mirror = Mirror.init(reflecting: self)
for (key, value) in mirror.children {
print("\(key!): \(value)")
}
}
关于UIApplication的keyWindow
- 如果设置了project的
Main Interface
为某个storyboard,则application: didFinishLaunchingWithOptions:
时,会某人创建一个window,并且设置如下:
window.rootViewController = storyboard.initialViewController;
等完成启动并
return YES
,之后才会将[window makeKeyAndVisible]
,而这时如果你想在该启动方法中进行一些UI方面的操作是不行的,因为它还没有被makeKeyAndVisible
。正确的做法是手动设置可见
[window makeKeyAndVisible]
,然后做一些操作:
[_window makeKeyAndVisible];
// 广告图
UIImageView *adIV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
adIV.backgroundColor = [UIColor redColor];
adIV.center = _window.center;
[_window addSubview:adIV];
- 另外,如果你没有设置项目的
Main Interface
,那么你需要手动实例化window对象,并设置rootViewController和makeKeyAndVisible:
_window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds];
[_window makeKeyAndVisible];
_window.rootViewController = [UIViewController new];
LaunchScreen.storyboard替换图片资源后无法显示问题
- 解决办法:
Simulator > Reset Content and Settings...
- 解决办法:
- 然后重新运行项目
UICollectionView上cell的布局属性
UICollectionViewLayoutAttributes *attrs =
[collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
UITextField事件
UIControlEventEditingDidBegin
UIControlEventEditingChanged
UIControlEventEditingDidEnd
UIControlEventEditingDidEndOnExit
OC Block
@property(nonatomic,copy)void (^valueChanged)(HYRegisterInputView *input);
UITextView超链接
UITextView *textView = [[UITextView alloc] init];
textView.scrollEnabled = NO;
textView.editable = NO;
textView.textContainer.lineFragmentPadding = 0;
textView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);
textView.delegate = self;
//代理方法
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange
{
return YES;
}
CocoaPods
a pod project builds all the individual pods as their own framework, and then combines them into one single framework: Pods-XXX.
https://cocoapods.org
https://opensource.org
Storyboard使用framework注意
When using storyboards, references to custom classes need to have both the class name and module set in the Identity Inspector. At the time of this storyboard’s creation, ThreeRingView was in the app’s module, but now it’s in the framework.
Swift Code-Access-Control
Public: for code called by the app or other frameworks, e.g., a custom view.
Internal: for code used between functions and classes within the framework, e.g., custom layers in that view.
By default, Swift makes everything internal or visible only within its own module.
Fileprivate: for code used within a single file, e.g., a helper function that computes layout heights.
Private: for code used within an enclosing declaration, such as a single class block. Private code will not be visible to other blocks, such as extensions of that class, even in the same file, e.g., private variables, setters, or helper sub-functions.
StatusBar网络请求指示器
UIApplication.shared.isNetworkActivityIndicatorVisible = true
SourceTree暂存
布局优先级priority
App Dynamic Type
- 设置>辅助功能>LargeText>支持动态字体的应用的阅读字体body
// 代码设置
label.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.headline)
// 字体改变通知
NotificationCenter.default.addObserver(forName: .UIContentSizeCategoryDidChange, object: .none, queue: OperationQueue.main) { [weak self] _ in
self?.tableView.reloadData()
}
NotificationCenter须知
Starting with iOS 9, it is no longer necessary to remove notification center observers. If your app’s deployment target is iOS 8, however, you will still need to do that!
- iOS9以后,不再需要手动移除通知中心中add的observer
UITableView刷新
// Row insertion/deletion/reloading.
open func beginUpdates() // allow multiple insert/delete of rows and sections to be animated simultaneously. Nestable
open func endUpdates() // only call insert/delete/reload calls or change the editing state inside an update block. otherwise things like row count, etc. may be invalid.
UITextView自动高度
Disabling scrolling is of similar importance to setting a label to 0 lines
textView.isScrollEnabled = false; //类似于lable.numberOfLines = 0;
归档
- swift3 - 只能对class类型归档
class Album: NSObject, NSCoding{
var title = ""
func encode(with aCoder: NSCoder) {
aCoder.encode(title, forKey: "title")
}
required init?(coder aDecoder: NSCoder) {
title = aDecoder.decodeObject(forKey: "title") as? String ?? ""
}
}
- swift4 - 对class、enum、struct类型归档
struct Album: Codable {
let title : String
}
// encode
func saveAlbums() {
let url = documents.appendingPathComponent(Filenames.Albums)
let encoder = JSONEncoder()
guard let encodedData = try? encoder.encode(albums) else {
return
}
try? encodedData.write(to: url)
}
// decode
func getAlbums(){
let savedURL = documents.appendingPathComponent(Filenames.Albums)
var data = try? Data(contentsOf: savedURL)
if data == nil, let bundleURL = Bundle.main.url(forResource: Filenames.Albums, withExtension: nil) {
data = try? Data(contentsOf: bundleURL)
}
if let albumData = data,
let decodedAlbums = try? JSONDecoder().decode([Album].self, from: albumData) {
albums = decodedAlbums
saveAlbums()
}
}
KVO
- swift4
private var valueObservation: NSKeyValueObservation!
valueObservation = coverImageView.observe(\.image, options: [.new]) { [unowned self] observed, change in
if change.newValue is UIImage {
// do something...
}
}
- swift3
coverImageView.addObserver(self, forKeyPath: "image", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "image",
change?[.newKey] is UIImage {
// do something...
}
}
UITable趣谈
Here’s a pseudo-explanation of what goes on when you create a new UITableView:
Table: Here I am! All I want to do is SHOW CELLS. Hey, how many sections do I have?
Data source: One!
Table: OK, nice and easy! And how many cells in that first section?
Data source: Four!
Table: Thanks! Now, bear with me, this might get a bit repetitive. Can I have the cell at section 0, row 0?
Data source: Here you go!
Table: And now section 0, row 1?
…and so on.
Swift元组类型Tuple
typealias AlbumData = (title: String, value: String)
let data = AlbumData(title: "xxx", value: "xxx")
// 也可以写成
let data = AlbumData("xxx", "xxx")
导航栏左侧按钮设置
- 返回按钮
// 默认是nil,在父controller中设置
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"返回" style:UIBarButtonItemStylePlain target:nil action:nil];
- 左边按钮
UIBarButtonItemStylePlain //细
UIBarButtonItemStyleDone //粗
// 保留返回按钮
self.navigationItem.leftItemsSupplementBackButton = true;
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"Left" style:UIBarButtonItemStyleDone target:self action:@selector(action:)];
CSR申请
URL特殊字符编码
"".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
Git关于.xcuserstate不提交的方法
1. cd 项目工程目录下
2. git rm --cached 项目名.xcodeproj/project.xcworkspace/xcuserdata/用户名.xcuserdatad/UserInterfaceState.xcuserstate
// 重新提交改变到本地
3. git commit -m "removed `.xcuserstate` file;"
UILayoutGuide
UIView
- layoutMarginsGuide
- readableContentGuide
UIViewController
- topLayoutGuide: UILayoutSupport
(topAnchor、bottomAnchor、heightAnchor)
- bottomLayoutGuide
NSLayoutAnchor
- UIView
NSLayoutXAxisAnchor
- leadingAnchor
- trailingAnchor
- leftAnchor
- rightAnchor
- centerXAnchor
NSLayoutYAxisAnchor
- topAnchor
- bottomAnchor
- centerYAnchor
- firstBaselineAnchor
- lastBaselineAnchor
NSLayoutDimension
- widthAnchor
- heightAnchor
UIView的appearance配置
[UIActivityIndicatorView appearanceWhenContainedIn:[MBProgressHUD class], nil].color = UIColor.whiteColor;
strongSelf与weakSelf
- 异步线程请求
var net: Network? = Network()
net!.request()
net = nil //线程回调的时候已经被销毁了,会出错,或无提示
class Network: NSObject {
func request() {
print("requesting...")
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.seconds(3)) {
[weak self] in
guard let strongSelf = self else {
fatalError("`self is nil`")
}
//如果有多步后续操作的话,就不能少了上述`guard let`判断而直接用self?.updateModel
strongSelf.updateModel()
strongSelf.updateUI()
}
}
func updateModel(){
print("updated model")
}
func updateUI(){
print("updated model")
}
}
UITableView横向滚动
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = self.view.bounds.width
self.tableView.isPagingEnabled = true
self.tableView.separatorStyle = .none
// 旋转
self.view.transform = CGAffineTransform(rotationAngle: .pi/2)
}
override func viewDidAppear(_ animated: Bool) {
self.tableView.scrollToRow(at: IndexPath.init(row: 9, section: 0), at: .bottom, animated: false)
super.viewDidAppear(animated)
}
}
extension ViewController{
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
cell.contentView.transform = CGAffineTransform(rotationAngle: -.pi/2)
return cell
}
}
WKWebView进度条
// 监听进度条
wkWebView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress",
let progress = change?[.newKey] as? Float {
// 显示进度
progressView.progress = progress == 1 ? 0 : progress
}
}
iOS文字删除线
NSStrikethroughStyleAttributeName: @(NSUnderlineStyleSingle|NSUnderlinePatternSolid),
NSStrikethroughColorAttributeName: [UIColor lightGrayColor]
OC可视化
IB_DESIGNABLE
@interface CircleView : UIView
@property (nonatomic, assign) IBInspectable CGFloat radius;
@end
NSLayoutConstraint基本用法
- (void)viewDidLoad {
[super viewDidLoad];
self.view.translatesAutoresizingMaskIntoConstraints = false;
UIButton *btn1 = [UIButton buttonWithType:UIButtonTypeSystem];
[btn1 setTitle:@"Button1" forState:UIControlStateNormal];
btn1.backgroundColor = [UIColor redColor];
btn1.translatesAutoresizingMaskIntoConstraints = false;
UIButton *btn2 = [UIButton buttonWithType:UIButtonTypeSystem];
[btn2 setTitle:@"Button2" forState:UIControlStateNormal];
btn2.backgroundColor = [UIColor greenColor];
btn2.translatesAutoresizingMaskIntoConstraints = false;
[self.view addSubview:btn1];
[self.view addSubview:btn2];
NSDictionary *views = NSDictionaryOfVariableBindings(btn1, btn2);
// 等宽
NSArray *cons1 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[btn1(btn2)]" options:0 metrics:nil views:views];
NSArray *cons2 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[btn1(44)]" options:0 metrics:nil views:views];
// 等高
NSArray *cons3 = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[btn2(btn1)]" options:0 metrics:nil views:views];
NSArray *cons4 = [NSLayoutConstraint constraintsWithVisualFormat:@"H:[btn1][btn2]|" options:0 metrics:nil views:views];
NSLayoutConstraint *top = [NSLayoutConstraint constraintWithItem:btn2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:btn1 attribute:NSLayoutAttributeTop multiplier:1 constant:0];
[self.view addConstraints:cons1];
[self.view addConstraints:cons2];
[self.view addConstraints:cons3];
[self.view addConstraints:cons4];
[self.view addConstraint:top];
}
多个UIButton点击选中一个,并改变样式
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.layer.borderWidth = 1;
btn.layer.cornerRadius = 5;
[btn setTitle:title forState:UIControlStateNormal];
[btn setTitle:title forState:UIControlStateSelected];
[btn setTitleColor:[UIColor grayColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor orangeColor] forState:UIControlStateSelected];
[btn addTarget:self action:@selector(actionForButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
btn.tintColor = [UIColor clearColor];
// 点击事件
for (UIView *sub in self.subviews)
{
if ([sub isKindOfClass:[UIButton class]])
{
UIButton *btn = (UIButton*)sub;
btn.selected = [btn isEqual: sender];
btn.layer.borderColor = btn.selected ? [UIColor orangeColor].CGColor : [UIColor lightGrayColor].CGColor;
}
}
UIScrollView约束
首先满足:frame[left, top, bottom, right]
其次要通过子view约束[left + right + size]水平滚动,[top + bottom + size]垂直滚动,来确定它的contentSize
(NSSet<UITouch *> *)touches取元素
UITouch *touch = touches.anyObject;
sending const NSString *
to parameter of type NSString *
discards qualifiers
static const NSString* str = @"xxx";
改成 static NSString* const str = @"xxx";
// 前者相当于指针本身不可修改,后者表示指针指向的内容不可修改,两者的作用都是使str只只读。
UITableViewStylePlain使得sectionHeader“悬浮”问题解决
#pragma mark - UIScrollViewDelegate
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat height = _tableView.sectionHeaderHeight;
CGFloat offY = scrollView.contentOffset.y;
if (offY >= 0 && offY <= height) {
scrollView.contentInset = UIEdgeInsetsMake(-offY, 0, 0, 0);
}
else if (offY >= height) {
scrollView.contentInset = UIEdgeInsetsMake(-height, 0, 0, 0);
}
}
UIButton.system选中设置
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn sizeToFit]; // 如果未设置btn.frame时应该这么用
btn.tintColor = [UIColor clearColor];
[btn setTitle:@"编辑" forState:UIControlStateNormal];
[btn setTitle:@"完成" forState:UIControlStateSelected];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[btn setTitleColor:[UIColor whiteColor] forState:UIControlStateSelected];
关于删除cell
[_dataSource removeObjectAtIndex:index];
[_tableView beginUpdates];
[_tableView deleteRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:0 inSection:index]] withRowAnimation:UITableViewRowAnimationLeft];
// 如果是最后一行,则要删除对应的section
[_tableView deleteSections:[NSIndexSet indexSetWithIndex:index] withRowAnimation:UITableViewRowAnimationAutomatic];
[_tableView endUpdates];
mas_makeConstraints 与 mas_updateConstraints
注意在tableView.rowHeight = UITableViewAutomaticDimension时,尽量不要选用update方式,因为要动态计算高度,需一开始指定约束。