Hello Li

lx的个人博客


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

React更新props与echarts实际应用

发表于 2018-06-19   |   分类于 React   |   阅读次数

关于Echarts初始化

在react项目中引用Echarts组件,需要在 render()方法中返回一个div元素。因为Echarts底层使用Canvas绘图,所以div元素的宽高一定是已知的。列如:

.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render() {
return (
<div id="pieCharts" className={styles.pieCharts}>
</div>
)
}

componentDidMount() {
let myChart = echarts.init(document.getElementById('pieCharts'))
let options = this.setOptions()
myChart.setOption(options)
}

setOptions() {
const {take_off, spray_off, spray_on, hand, total} = this.props

/*
该方法中拿到父组件传递过来的props之后,开始巴拉巴拉按照echarts官方文档设置需要的参数。
*/
}

.less

1
2
3
4
5
.pieCharts {
width: 400px;
height: 250px;
margin: auto;
}

动态更新Echarts数据参数

如果该组件只是静态去展示某个时间段的数据,完全满足需求。但是如果是动态的去生成数据时,也就是props每时每刻都在改变,那么会发现echarts所展示的数据根本没有发现变化。我们都知道componentDidMount方法只会执行一次,就是在组件挂载完成之后调用。解决该问题就需要用到更新props或者state那一套流程。(React生命周期参考React官网)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shouldComponentUpdate(nextProps) {
if (nextProps.take_off !== this.take_off ||
nextProps.spray_off !== this.spray_off ||
nextProps.spray_on !== this.spray_on ||
nextProps.hand !== this.hand) {
return true
}
return false
}

componentDidUpdate() {
const dom = document.getElementById('pieCharts')
var myChart = echarts.getInstanceById(dom.getAttribute('_echarts_instance_'));
myChart.setOption(this.setOptions())
}

shouldComponentUpdate判断当前组件是否需要更新,一般多用于性能优化,减少不必要的组件刷新次数。在本例中,比较父组件传递过来的props与上一次props的异同。当返回true时,我们在componentDidUpdate方法中取到echarts的实例,重新对echarts设置数据参数。

小结

需求不难实现,最主要的还是要对React的生命周期有详细的认知。

React生命周期

参考

ReactJs component lifecycle methods — A deep dive
如何通过dom对象获取echarts对象

算法小集(持续更新)

发表于 2018-03-20   |   分类于 算法   |   阅读次数

排序

桶排序

OC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
NSArray *bucketSort(NSArray *list){

NSMutableArray *a = @[].mutableCopy;
NSMutableArray *c = [NSMutableArray array];

//初始化数组a中所有元素都为0
for (NSInteger i = 0; i <= 10; i++) {
a[i] = @0;
}

//遍历b中所有元素 当出现数组a中出现b中元素 数组a的这个元素就+1
for (NSNumber *num in list) {

NSInteger index = [num integerValue];
//index = 5,2,3,1,8

if (a[index]) {
a[index] = @([a[index] integerValue] + 1);
} else {
a[index] = @0;
}

}

//遍历数组a

//从大到小
// for (NSInteger i = 10; i < a.count; i --) {
// //找到数组a中被增加的元素
// if ([a[i] integerValue] > 0) {
// [c addObject:@(i)];
// }
// }

//从小到大
for (NSInteger i = 0; i <= 10; i++) {
if ([a[i] integerValue] > 0) {
[c addObject:@(i)];
}
}

if (c.count > 0) {
return c;
} else {
return nil;
}
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function bucketSort(list) {
if (!Array.isArray(list)) return;

let arrayA = []
let arrayC = []

for (let index = 0; index < 10; index++) {
arrayA[index] = 0
}

list.forEach(element => {
if (element == undefined || element == null) return

if (arrayA[element] != undefined || arrayA[element] != null) {
arrayA[element] += 1
} else {
arrayA[element] = 0
}
})

return arrayA.map((item, index) => {
if (item > 0) return index
})

}

快速排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
NSArray *quicksort(NSMutableArray *array, NSInteger left, NSInteger right){

if (left > right) {
return nil;
}

//temp 基准数
NSInteger temp = [array[left] integerValue];
NSInteger t = 0;

while (left != right) {
//从右向左找
while ([array[right] integerValue] > temp && left < right) {
right --;
}

//从左向右找
while ([array[left] integerValue] < temp && left < right) {
left ++;
}

//未相遇
if (left < right) {
//交换位置
t = [array[left] integerValue];
array[left] = array[right];
array[right] = @(t);
}
}

//left == right 确定基准数位置
array[left] = @(temp);

//递归
//处理基准数左边数据
quicksort(array, left, left - 1);
//处理基准数右边数据
quicksort(array, left + 1, right);

return array;
}

冒泡排序

OC

1
2
3
4
5
6
7
8
9
10
11
12
13
NSArray *bubblesort(NSMutableArray *list){
NSNumber *t;
for (NSInteger i = 0; i < list.count - 1; i++) { //n个数排序,只用进行n-1趟
for (NSInteger j = 0; j < list.count - 1; j++) { //从第1位开始比较直到最后一位尚未归为的数 因为只进行n-1趟排序,所以最后一位则是n-1。
if (list[j] > list[j + 1]) {
t = list[j];
list[j] = list[j+ 1];
list[j + 1] = t;
}
}
}
return list;
}

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function bubbleSort(array) {
if (!Array.isArray(array)) return

let T = 0;
for (let index = 0; index < array.length - 1; index++) {
for (let j = 0; j < array.length -1; j++) {
if (array[j] > array[j + 1]) {
T = array[j]
array[j] = array[j + 1]
array[j + 1] = T
}
}
}
return array
}

查找

二分查找

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function binary_search(list, item) {
let low = 0
let high = list.length - 1
while (low <= high) {
const mid = Math.floor((low + high ) / 2)
const guess = list[mid]
if (guess == item) {
return mid
}
if (guess > item) {
high = mid - 1
} else {
low = mid + 1
}
}
return null
}

AutoLayout - 父视图根据子视图布局自适应大小

发表于 2017-09-01   |   分类于 Swift   |   阅读次数

前言

本文记录AutoLayout日常布局UI时的技巧。每当拿到新的界面设计时,比

如这样的:

当某个字段的长度不确定时,(图1中的“作业地址”,图2中”预约地址”和“取消原因”)。Label的行数就要设置为0或者 >=1。总试图的高度就不确定。于是乎就要开始就算containerView的高度了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (CGFloat)cellHeight{
CGFloat publicHeight = kViewTopPadding + kImageSize().height + kViewTypeMargin + _nameStrSize.height + kViewTypePadding + _nameTextSize.height + kViewTypeMargin / 2 + kCellBottomPadding;
switch (_layoutViewType) {
case ViewLayoutBreviaryType:
return publicHeight;
break;
case ViewLayoutDetailType:
//判断当前model存储的信息是待执行还是已取消状态
if (_orderModel.reserveType == YWBOrderReservationToBeConfirm) {
return publicHeight + kViewTypeMargin / 2 + _addressStrSize.height + kViewTypePadding + _addressTextSize.height + kViewTypeMargin + _typeStrSize.height + _typeTextSize.height + kViewTypeMargin + kButtonHeight + kViewBottomPadding + kCellBottomPadding;
}else if(_orderModel.reserveType == YWBOrderReservationCancledForSaler || _orderModel.reserveType == YWBOrderReservationCancledForClient){
return publicHeight + kViewTypeMargin / 2 + _addressStrSize.height + kViewTypePadding + _addressTextSize.height + kViewTypeMargin + _cancleStrSize.height + kViewTypePadding + _cancleTextSize.height + kViewBottomPadding + kCellBottomPadding;
}
break;
}
return 0.0;
}
1
2
3
4
5
6
7
8
- (CGFloat)heightWithWidth:(CGFloat)width font:(UIFont *)font
{
CGRect attrsRect = [self boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX)
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
attributes:@{NSFontAttributeName : font}
context:nil];
return attrsRect.size.height;
}

如果不细心漏掉某一处高度,查找起来也比较麻烦。每次算高度是我不爱干的一件事情。没有技术含量😂。完全就是细心活。多亏AutoLayout能让我们从这一毫无兴趣的工作中解放出来。

父视图自适应

新建Xcode工程。在Main.storyboard的ViewController中随意拖入view控件,颜色暂定为blue。放置3个Label控件。

1.png

  1. 先不设置blueview的约束。
  2. 逐步设置Label的约束。

黑色Label约束如下:

2.png

在这里我设置了黑色Label的size。这个并无关系,可设可不设。使用Label的固有大小也行。

黄色Label约束如下:

3.png

红色Label约束如下:

4.png

Label的行数都为0。

重点

Label一定要设置距离父视图的间距。这样AutoLayout才能计算出父视图的大小。

最重要的一步

看下view的约束
5.png
6.png
将view的固有尺寸设置为placeholder。 我们都知道Label Button ImageView(已经设置image属性)这些控件,在用AutoLayout布局时,只需要设置外边距就行了。AutoLayout会自动适应size大小。这就是固有尺寸。反观View就不行了。假如只设置View的外边距,这时一定会报出约束错误。但是View作为容器。它能根据子控件的大小来自适应。只需要将其固有尺寸(size)都设置为0。

这是对于高度不固定的来讲。如果宽度不固定呢?答案是同样适用,一般来说,view的宽度是固定的。高度不固定。

需要注意的问题

如果不设置view的左右边间距或者宽度你就会发现变成了这样:
7.png
view的左右边距已经被Label撑到屏幕外面了。因为这时view的宽度也是会随着Label变化的。
所以说我设置了view的左右边距(或者设置其固定的宽度)

验证

接来下就可以设置Label的text属性了。随便输入汉字来验证一下:
8.png
9.png

可以发现view的高度已经随着Label的变化而变化了。其他的控件以此类推。都是一样的道理。另外纯代码布局也同样适用。

总结

view作为容器类其承载其他控件的显示。只要设置了子控件的距离自己的约束。并将自己的固有尺寸设置为placeholder(也就是width和height都为0)。这样AutoLayout就能自动计算出view的大小。

补充

怎么样拿到view的大小呢?系统为我们提供了这样的API

1
2
3
4
5
6
7
8
9
10
11
extension UIView {

/* The size fitting most closely to targetSize in which the receiver's subtree can be laid out while optimally satisfying the constraints. If you want the smallest possible size, pass UILayoutFittingCompressedSize; for the largest possible size, pass UILayoutFittingExpandedSize.
Also see the comment for UILayoutPriorityFittingSizeLevel.
*/
@available(iOS 6.0, *)
open func systemLayoutSizeFitting(_ targetSize: CGSize) -> CGSize // Equivalent to sending -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: with UILayoutPriorityFittingSizeLevel for both priorities.

@available(iOS 8.0, *)
open func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize
}

在viewDidLayoutSubviews或者layoutSubviews中通过调用上面API就能够获得通过AutoLayout计算过后view的大小。

React Native 自定义组件

发表于 2017-07-10   |   分类于 React Native   |   阅读次数

方法一:

自定义组件(Button)代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class Button extends Component{
render(){
//解构
const {title,onClick} = this.props;
return(
<TouchableOpacity style={styles.touchStyle}
onPress={onClick}
>
<Text style={styles.textStyle}>{this.props.title}</Text>
</TouchableOpacity>
)
};
}

export default关键字:导出自定义组件,可以被别的js文件所引用。如果没有关键字,就只能在本js文件使用该类。例如在Login.js文件中可以用

1
import Button from './Button'

导入Button组件

1
2
//解构
const {title,onClick} = this.props;

解构从父组件的传过来的props, 子组件拿到父组件设置的属性值,显示传递的值或者处理传递过来的方法。

列如上面代码:

1
2
3
4
5
6
7
8
///显示Button的title
<Text style={styles.textStyle}>{this.props.title}>
</Text>

///处理TouchableOpacity的点击事件
<TouchableOpacity style={styles.touchStyle}
onPress={onClick}>
</TouchableOpacity>

父组件调用代码: 传递title信息和onClick方法回调

1
2
<Button title="登录"
onClick={() => alert('点击了登录按钮')}/>

方法二

与方法一的区别不大

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { Component,PropTypes } from 'react';
import { Text,TouchableOpacity,StyleSheet } from 'react-native';

export default class Button extends Component{
static propTypes = {
title: PropTypes.string,
onPress: PropTypes.func
}

render(){
return(
<TouchableOpacity style={styles.touchStyle}
onPress={()=>{
if (this.props.onPress) {
this.props.onPress(this)
}
}}
>
<Text style={styles.textStyle}>{this.props.title}</Text>
</TouchableOpacity>
)
};
}

其中

1
2
3
4
static propTypes = {
title: PropTypes.string,
onPress: PropTypes.func
}

暴露出组件的属性(title)和方法(onPress)。

1
2
3
4
5
6
7
8
9
<TouchableOpacity style={styles.touchStyle}
onPress={()=>{
if (this.props.onPress) {
this.props.onPress(this)
}
}}
>
<Text style={styles.textStyle}>{this.props.title}</Text>
</TouchableOpacity>
  • 处理TouchableOpacity的点击事件,设置回调函数
  • 显示Button的title

组件调用:

1
2
<Button  title="登录"
onPress={() => alert('点击了登录按钮')}/>

Demo

RNCustomComponent.gif

记一次React Native的StackNavigator使用小结

发表于 2017-07-06   |   分类于 React Native   |   阅读次数

初始化

1
2
3
4
export default (
routeConfigMap: NavigationRouteConfigMap,
stackConfig: StackNavigatorConfig = {}
)

在StackNavigator的初始化中需要传入两个参数:routeConfigMap 表示路由配置参数, stackConfig表示配置StackNavigatorConfig一些样式或者属性。

1
2
3
4
5
6
7
8
9
10
11
12
const MainSrceenStackNavigator = StackNavigator(
{
MainScreenNavigator: {screen: MainScreenNavigator},
DiscoverDetail: {screen: DiscoverDetail},
SettingDetail: {screen: SettingDetail}
},
{
initialRouteName: 'MainScreenNavigator',
mode:'card',
headerMode:'screen',
}
);

上述代码块配置了3个不同路由的界面,initialRouteName设置根导航控制器界面,默认第一个为根路由。mode表示界面的跳转方式:push或者模态弹出。headerMode表示界面在做跳转时的区域。有三种方式:float,screen,none。float表示不包括导航条在内;screen表示整个屏幕(包括导航条);none表示导航条的隐藏。还有很多属性可选配置。用到可在官网查询React Navigation 。

🌰

在我的界面配置导航条样式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static navigationOptions = ({navigation})=>({
// tabBarLabel: '我的',
title: '我',
headerStyle:{
backgroundColor: 'black'
},
headerTitleStyle:{
color: 'white'
},
tabBarIcon:({tintColor,focused}) => (
<TabBarItem
tintColor={tintColor}
focused={focused}
normalImage={require('./source/我的@2x.png')}
selectedImage={require('./source/我的-选中@2x.png')}
/>
),
});

然后再显示出ListView(三个分区的ListView)

1
2
3
4
5
6
7
8
9
constructor(props) {
super(props);

var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2,sectionHeaderHasChanged:(s1,s2)=>s1 !== s2});
this.state = {
dataSource: ds.cloneWithRowsAndSections([['钱包'],['收藏','相册','卡包','表情'],['设置']]),
};

}
1
2
3
4
5
6
7
8
9
10
11
render(){
return (
<View style={styles.container}>
<ListView
dataSource={this.state.dataSource}
renderRow={this._renderRow.bind(this)}
renderSeparator={this._renderSeparator}>
</ListView>
</View>
)
}

分割线

1
2
3
4
5
6
7
_renderSeparator(sectionID, rowID, adjacentRowHighlighted)  {
if(sectionID == 1){
return (
<View style={styles.separatorStyle}></View>
)
}
}

ListView每个分区每一行的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
_renderRow(rowData, sectionID, rowID, highlightRow){
if (sectionID == 0) {
return(
<TouchableOpacity onPress={() =>{
highlightRow(sectionID,rowID),
this.props.navigation.navigate('SettingDetail',{'name': rowData})
}}>
<View style={styles.section_0_style}>
<Text style={{fontSize:15}}>{rowData}</Text>
</View>
</TouchableOpacity>
)
}else if(sectionID == 1){
return(
<TouchableOpacity onPress={() =>{
highlightRow(sectionID,rowID),
this.props.navigation.navigate('SettingDetail',{'name': rowData})
}}>
<View style={styles.viewStyle}>
<Text style={{fontSize:15}}>{rowData}</Text>
</View>
</TouchableOpacity>
)
}else{
return(
<TouchableOpacity onPress={() =>{
highlightRow(sectionID,rowID),
this.props.navigation.navigate('SettingDetail',{'name': rowData})
}}>
<View style={styles.section_0_style}>
<Text style={{fontSize:15}}>{rowData}</Text>
</View>
</TouchableOpacity>
)
}
}

this.props.navigation.navigate('SettingDetail',{'name': rowData}) navigation可用属性来获取。 该函数传递参数{rowData}即ListView每行数据到SettingDetail界面。

再SettingDetail界面中可用下面方法

1
2
3
4
static navigationOptions = ({navigation}) => ({
title:`${navigation.state.params.name}`,
headerBackTitle: '返回',
})

来获取传递过来的参数显示到导航条的title。

RN.gif

iOS界面优化系列之:处理图片缩放问题

发表于 2017-02-13   |   分类于 Objective-C   |   阅读次数

在众多阅读类的APP中,比如:




这类比较稍微简单的界面中充斥着大量的的图片,多则6张,少则3-4张。再者像微博这类重型社交的APP,有的cell都已经有9张图片。图片的优化程度对整个界面的流畅度来说都是重中之重。(另外一个也很重要的地方就是文本的异步渲染),后几个系列也会说到。

像这种异步加载图片的开源库有很多:SD_WebImageView YYWebImage Kingfisher PINRemoteImage …
开发者如果只是简单的调用一下 sd_setImageWithURL: 图片能够很好的显示出来。假如从网络加载的图片与自己设置的UIImageView的尺寸不一致,就会导致这种结果。

Jietu20170112-131912.png
如果是真机调试 打开Instruments中Core Animation
Jietu20170112-135233.png
在右侧栏中打开
Jietu20170112-135327.png
如果是模拟器,打开Debug ->Color Misaligned Images

这个选项检查了图片是否被缩放,以及像素是否对齐。被放缩的图片会被标记为黄色,像素不对齐则会标注为紫色。**黄色、紫色越多,性能越差。

解决办法:
1.与后台协商,让后台返回的图片尺寸与客户端UIImageView尺寸一致;
2.自己处理。

下面介绍下如何自己处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
extension UIImage {
    func resizeImage(_ image: UIImage, targetSize: CGSize) -> UIImage? {
            let newRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height).integral
            UIGraphicsBeginImageContextWithOptions(targetSize, false, 0)
            if let context = UIGraphicsGetCurrentContext() {
                context.interpolationQuality = .high
                let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: targetSize.height)
                context.concatenate(flipVertical)
                context.draw(self.cgImage!, in: newRect)
                let newImage = UIImage(cgImage: context.makeImage()!)
                UIGraphicsEndImageContext()
               return newImage
            }
        return nil
    }
}

很简单就是网络下载下来的图片按照自己设置的UIImageView的尺寸来重新draw一个。

1
2
3
4
5
6
7
func setImage<T: UIImageView>(_ imageView: T, URLString: String, targetSize: CGSize){
        imageView.pin_setImage(from: URL.init(string: URLString), completion: {(PINRemoteImageManagerResult) in
            if let image = PINRemoteImageManagerResult.image{
                    imageView.image = image.resizeImage(image, targetSize: targetSize)
            }
        })
    }

重新给UIImageView设置image属性

重新打开Color Misaligned Images功能来检测一下
Jietu20170112-140353.png

嗯! 不错哦,之前的黄色没有了。 本以为就达到了的要求。

但是用Instruments中Time Profiler
Jietu20170112-140732.png
来检测下处理该函数所需要的时间

Jietu20170112-141255.png

用Core Animation来测下FPS

Jietu20170112-142314.png

虽然已经很流畅了(注:文本用到了异步绘制), 基本在54左右, 但是距离满帧59 - 60还差那么一丢丢。

再来观察给UIImage扩展的这个方法 发现它是在主线程中执行,当快速滑动表格时,会有大量的图片在主线程中重新绘制。这个是造成缺失那几帧的主要原因。

解决办法:那就在子线程总去draw, 在主线程中去显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
extension UIImageView {
func drawImage(_ image: UIImage, targetSize: CGSize) {
DispatchQueue.global().async {
let newRect = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height).integral
UIGraphicsBeginImageContextWithOptions(targetSize, false, 0)
if let context = UIGraphicsGetCurrentContext() {
context.interpolationQuality = .high
let flipVertical = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: targetSize.height)
context.concatenate(flipVertical)
context.saveGState()
context.draw(image.cgImage!, in: newRect)
context.restoreGState()
let newImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
DispatchQueue.main.async {
if let newImage = newImage{
self.layer.contents = newImage.cgImage
}
}
}
}
}
}

Objective-C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(void)drawImage:(UIImage *)image targetSize:(CGSize )size{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
CGRect newRect = CGRectMake(0, 0, size.width, size.height);
CGRectIntegral(newRect);
UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGAffineTransform transform = CGAffineTransformMake(1, 0, 0, -1, 0, size.height);
CGContextConcatCTM(context, transform);
CGContextSaveGState(context);
CGContextDrawImage(context, newRect, image.CGImage);
CGContextRestoreGState(context);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (newImage) {
self.layer.contents = (id)(newImage.CGImage);
}
});
});
}

该方法也很简单,在子线程中去重新draw一个UIImage,在主线程中去显示。

调用:

1
2
3
4
5
6
7
8
func setImage<T: UIImageView>(_ imageView: T, URLString: String, targetSize: CGSize){
        imageView.pin_setImage(from: URL.init(string: URLString), completion: {(PINRemoteImageManagerResult) in
            if let image = PINRemoteImageManagerResult.image{
                imageView.drawImage(image, targetSize: targetSize)
//                    imageView.image = image.resizeImage(image, targetSize: targetSize)
            }
        })
    }

重新测下帧数以及Time Profile

Jietu20170112-143643.png
当快速滑动的时候基本上能够达到慢帧的状态

Jietu20170112-143924.png
所用的时间也是非常少的。在16ms以内。

dispatch_semaphore_t 信号量

发表于 2016-12-08   |   分类于 Objective-C   |   阅读次数

dispatch_semaphore_create

dispatch_semaphore_create(long value) 传入的是一个数字,该数字必须大于等于0。列如传入10,表示创建一个信号总量为10信号量(就好比一共有10个停车位)。假如传入0

The starting value for the semaphore. Passing a value less than zero will cause NULL to be returned.

则返回为NULL

dispatch_semaphore_wait

该函数会使传入的信号量减1,当信号量有 >= 1时,就会继续往下执行。如果没有,该线程就会一直处于等待状态,可阻塞当前线程。该函数需要传入另一个参数

#define DISPATCH_TIME_NOW 表示从现在开始 #define DISPATCH_TIME_FOREVER 表示永不超时

dispatch_semaphore_single

该函数会使传入的信号量加1,与dispatch_ semaphore _ wait成对出现。

拿YYKit源码来举例:在#import “UIButton+YYWebImage.m”中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
- (instancetype)init {
self = [super init];
//声明一个信号总量为1信号量
_lock = dispatch_semaphore_create(1);
_dic = [NSMutableDictionary new];
return self;
}

- (_YYWebImageSetter *)setterForState:(NSNumber *)state {

//是当前线程处于阻塞状态,直到有 >= 1 信号量才会继续执行下面的取值操作
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
//如果不加入线程锁,就很有可能发生多个线程同时访问_dic字典,造成资源竞争。
_YYWebImageSetter *setter = _dic[state];
//使该信号量加1。保持信号量的平衡,如果没有保持信号量的平衡,可能会出现莫名的bug,甚至crash。
dispatch_semaphore_signal(_lock);
return setter;
}


- (_YYWebImageSetter *)lazySetterForState:(NSNumber *)state {

//同理
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
_YYWebImageSetter *setter = _dic[state];
if (!setter) {
setter = [_YYWebImageSetter new];
_dic[state] = setter;
}
dispatch_semaphore_signal(_lock);
return setter;
}

在读取_YYWebImageSetter该函数中使用dispatch_semaphore_wait和dispatch_semaphore_signal使当前函数为线程安全。实际上信号量就是锁,它与 @synchronized 函数看起来很像。所起到的作用就是至始至终只会有一个线程去访问该函数。

在 这边文章中很形象的比喻了信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
关于信号量,一般可以用停车来比喻。

  停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。

  信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal

  就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value)),

  调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;

  当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主

  没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,

  所以就一直等下去。

举个🌰 NSMutableArray不是线程安全的,我们可以稍作修饰,线程安全的去更新数组

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NSMutableArray *array = [NSMutableArray array];
dispatch_semaphore_t semphore = dispatch_semaphore_create(1);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.lx.gcddemo",DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(100, concurrentQueue, ^(size_t i) {
dispatch_semaphore_wait(semphore, DISPATCH_TIME_FOREVER);
/*
@synchronized (array) {
[array addObject:[NSNumber numberWithUnsignedInteger:i]];
}
*/
[array addObject:[NSNumber numberWithUnsignedInteger:i]];
dispatch_semaphore_signal(semphore);
});
NSLog(@"%@",array);

     

cocoapods 更新到最新版本(1.1.0)

发表于 2016-10-14   |   分类于 其他   |   阅读次数

在Apple 发布swift3.0之后 一大部分著名的第三方开源库,比如Alamofire,snapKit 之后都陆续更新到swift3.0。当我们用cocoapods管理它们或者使用它们的时候都会看到这样一句话:

1
CocoaPods 1.1.0+ is required to build Alamofire 4.0.0+.
1
CocoaPods 1.1.0+ is required to build SnapKit 3.0.0+.

需要将cocoapods更新到1.1.0版本以上

1
gem install cocoapods --pre //升级到最新版本

但是会遇到权限问题

1
ERROR:  While executing gem ... (Errno::EPERM) Operation not permitted - /usr/bin/pod

需要这么一句话

1
sudo gem install cocoapods --pre -n /usr/local/bin cocoa pods

在10.11以上的系统中Apple已经启用无根的安装 stack overflow这么解释:

1
This is happening because Apple has enabled rootless on the new install

参考链接

查看cocoapods当前版本

1
pod --version
1
1.1.0.beta.2

此时cocoapods版本就会更新到最新版本.

iOS开发小技巧(一)

发表于 2016-10-10   |   分类于 Objective-C   |   阅读次数

改变UIImagePickerController的导航条以及title的颜色

1
2
3
4
5
6
7
8
9
10
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
UINavigationBar *bar = [UINavigationBar appearance];
NSMutableDictionary *titleDictionary = [NSMutableDictionary dictionary];
titleDictionary[NSForegroundColorAttributeName] = [UIColor whiteColor];
[bar setTitleTextAttributes:titleDictionary];
viewController.navigationController.navigationBar.barTintColor = DIF_BASE_COLOR;
viewController.navigationController.navigationBar.tintColor = [UIColor whiteColor];
}

利用KVC改变TextField的占位符的颜色

1
[self.nickNameTextField setValue:[UIColor whiteColor] forKeyPath:@"_placeholderLabel.textColor"];

TextView的字数限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#define MaxNumberOfDescriptionChars  150

-(void)textViewEditChanged:(NSNotification *)obj{
UITextView *textView = (UITextView *)obj.object;
NSString *toBeString = textView.text;
NSString *lang = [[UITextInputMode currentInputMode] primaryLanguage]; // 键盘输入模式
if ([lang isEqualToString:@"zh-Hans"]) { // 简体中文输入,包括简体拼音,健体五笔,简体手写
UITextRange *selectedRange = [textView markedTextRange];
//获取高亮部分
UITextPosition *position = [textView positionFromPosition:selectedRange.start offset:0];
// 没有高亮选择的字,则对已输入的文字进行字数统计和限制
if (!position) {
if (toBeString.length > MaxNumberOfDescriptionChars) {
textView.text = [toBeString substringToIndex:MaxNumberOfDescriptionChars];
}
}
// 有高亮选择的字符串,则暂不对文字进行统计和限制
else{
}
}
// 中文输入法以外的直接对其统计限制即可,不考虑其他语种情况
else{
if (toBeString.length > MaxNumberOfDescriptionChars) {

textView.text = [toBeString substringToIndex:MaxNumberOfDescriptionChars];
}
}
}



- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text{
NSString *new = [textView.text stringByReplacingCharactersInRange:range withString:text];
self.numLabel.text = [NSString stringWithFormat:@"%zd字",(MaxNumberOfDescriptionChars - new.length)];
if(new.length > MaxNumberOfDescriptionChars){
self.numLabel.text = [NSString stringWithFormat:@"%zd字",0];
if (![text isEqualToString:@""]) {
return NO;
}
}
return YES;
}

判断字符串或者集合类型是否为空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
+ (BOOL)isEmpty:(id )obj
{
if ([obj isKindOfClass:[NSString class]]) {
if(obj == nil || [obj class] == [NSNull null] || [obj isEqualToString:@""] || [obj isEqualToString:@"(null)"] || [obj isEqualToString:@"NULL"] || [obj isEqualToString:@"<null>"] || [obj isEqualToString:@"null"])
{
return YES;
}
return NO;
}
if (obj == nil || (NSNull *)obj == [NSNull null] || ([obj respondsToSelector:@selector(length)] && [obj length] == 0) || ([obj respondsToSelector:@selector(count)] && [obj count] == 0)){
return YES;
}
return NO;
}

给UIResponder添加indexPath属性

列如在cell中有button imageview 可以利用该属性找到cell中改该视图

objective - C

1
2
3
4
5
@interface UIResponder (Externtion)

@property (nonatomic, strong) NSIndexPath *indexPath;

@end
1
2
3
4
5
6
7
8
9
- (void)setIndexPath:(NSIndexPath *)indexPath
{
objc_setAssociatedObject(self, @selector(indexPath), indexPath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


- (NSIndexPath *)indexPath{
return objc_getAssociatedObject(self, @selector(indexPath));
}

swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
extension UIResponder{

private struct AssociatedKey {
static var AssociatedName = "AssociatedName"
}


public var indexPath: NSIndexPath?{
get{
return objc_getAssociatedObject(self, &AssociatedKey.AssociatedName) as? NSIndexPath
}

set{
if let newValue = newValue {
objc_setAssociatedObject(self, &AssociatedKey.AssociatedName, newValue as NSIndexPath , .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
}

UIRefreshControl

1
2
3
4
5
6
7
8
9
let refresh = UIRefreshControl.init()
refresh.tintColor = UIColor.red
refresh.attributedTitle = NSAttributedString.init(string: "正在加载")
refresh.addTarget(self, action: #selector(ViewController.refreshTabeleView(sender:)), for: UIControlEvents.valueChanged)


tableView.refreshControl = refresh
tableView.addSubview(refresh)
tableView.reloadData()

将HTML字符串转化为NSAttributedString富文本字符串

1
2
3
4
5
6
7
8
9
- (NSAttributedString *)attributedStringWithHTMLString:(NSString *)htmlString
{
NSDictionary *options = @{ NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
NSCharacterEncodingDocumentAttribute :@(NSUTF8StringEncoding) };

NSData *data = [htmlString dataUsingEncoding:NSUTF8StringEncoding];

return [[NSAttributedString alloc] initWithData:data options:options documentAttributes:nil error:nil];
}

CABasicAnimation(CAKeyframeAnimation)key path 取值

keyPath可以使用的key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- define angle2Radian(angle) ((angle)/180.0*M_PI)

transform.rotation.x 围绕x轴翻转 参数:角度 angle2Radian(4)

transform.rotation.y 围绕y轴翻转 参数:同上

transform.rotation.z 围绕z轴翻转 参数:同上

transform.rotation 默认围绕z轴

transform.scale.x x方向缩放 参数:缩放比例 1.5

transform.scale.y y方向缩放 参数:同上

transform.scale.z z方向缩放 参数:同上

transform.scale 所有方向缩放 参数:同上

transform.translation.x x方向移动 参数:x轴上的坐标 100

transform.translation.y x方向移动 参数:y轴上的坐标

transform.translation.z x方向移动 参数:z轴上的坐标

transform.translation 移动 参数:移动到的点 (100,100)

opacity 透明度 参数:透明度 0.5

backgroundColor 背景颜色 参数:颜色 (id)[[UIColor redColor] CGColor]

cornerRadius 圆角 参数:圆角半径 5

borderWidth 边框宽度 参数:边框宽度 5

bounds 大小 参数:CGRect

contents 内容 参数:CGImage

contentsRect 可视内容 参数:CGRect 值是0~1之间的小数

hidden 是否隐藏

position

shadowColor

shadowOffset

shadowOpacity

shadowRadius

隐藏导航条和tabBar的直线

1
2
3
UINavigationBar *bar = [UINavigationBar appearance];
bar.shadowImage = [UIImage new];
[bar setBackgroundImage:[UIImage new] forBarMetrics:UIBarMetricsDefault];
1
2
3
UITabBar *tabBar = [UITabBar appearance];
tabBar.shadowImage = [UIImage new];
tabBar.backgroundImage = [UIImage new];

全屏手势返回

1
2
3
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self.interactivePopGestureRecognizer.delegate action:@selector(handleNavigationTransition:)];
pan.delegate = self;
[self.view addGestureRecognizer:pan];
1
2
3
4
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return self.childViewControllers.count > 1;
}

UITextView的placeholder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)textViewDidBeginEditing:(UITextView *)textView {
[self resetTextViewPlaceholderWithText:textView];
}

- (void)textViewDidEndEditing:(UITextView *)textView {
[self resetTextViewPlaceholderWithText:textView];
}

- (void)resetTextViewPlaceholderWithText:(UITextView *)textView
{
if ([textView.text isEqualToString:@"要显示的placeholder"]) {
textView.text = @"";
textView.textColor = [UIColor blackColor];
return;
}
if (textView.text.length < 1) {
textView.text = @"要显示的placeholder";
textView.textColor = [UIColor colorWithRed:0.87 green:0.87 blue:0.87 alpha:1.00];
return;
}
}

让应用支持摇一摇

  • 开启摇一摇权限
1
[UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;
  • 让view/viewcontroller成为第一响应者
1
2
3
4
#pragma mark - UIResponder
- (BOOL)canBecomeFirstResponder {
return YES;
}
  • 实现方发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#pragma mark - 摇动
/**
* 摇动开始
*/
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event {
if (motion == UIEventSubtypeMotionShake) {
// do some thing
}
}

/**
* 摇动结束
*/
- (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {
}

/**
* 摇动取消
*/
- (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event {
}

HeaderView随tableview一起滑动

1
2
3
4
5
6
7
8
9
10
11
12
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
if (scrollView == self.tableView) {

CGFloat headerHeight = DIF_SCREEN_HEIGHT / 3;
if (scrollView.contentOffset.y <= headerHeight && scrollView.contentOffset.y >= 0) {
scrollView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
}else if (scrollView.contentOffset.y >= headerHeight){
scrollView.contentInset = UIEdgeInsetsMake(-headerHeight, 0, 0, 0);
}
}
}

load VS initialize

发表于 2016-10-09   |   分类于 Objective-C   |   阅读次数

共同点

方法只会被调用一次(相对于runtime而言)

区别

load方法

声明一个SuperClass类继承与NSObject

1
2
3
4
@implementation SuperClass
+ (void) load {
NSLog(@"加载load方法");
}
1
2
3
4
5
6
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"main");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

控制台打印输出 load main

得出结论:load方法在程序启动前就会调用。在main函数之前调用。

initialize方法

执行以下代码

1
2
3
4
5
6
7
+ (void) initialize {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

+ (void) load {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

控制台打印输出

1
2
SuperClass +[SuperClass initialize]
SuperClass +[SuperClass load]

为什么initialize方法会再load方法前调用呢?通过Apple的文档

1
The runtime sends initialize to each class in a program exactly one time just before the class, or any class that inherits from it, is sent its first message from within the program. (Thus the method may never be invoked if the class is not used.) The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses.

得知: initialize方法在类的第一个方法被调用前调用。即先执行load方法中的[self class] 就会调用initialize方法。

子类会调用父类的initialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface ChildClass : SuperClass

@end


@implementation SuperClass

+ (void) initialize {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

@end


@implementation ChildClass

+ (void)load
{
NSLog(@"%@ %s", [self class], __FUNCTION__);

}

@end

还是导入头文件而不去使用它控制台打印输出

1
2
3
SuperClass +[SuperClass initialize]
ChildClass +[ChildClass initialize]
ChildClass +[ChildClass load]

类别(Category)中的+(void)load的+(void)initialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/******* Interface *******/
@interface MainClass : NSObject
@end

/******* Category Implementation *******/
@implementation MainClass(Category)

+ (void) load {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

+ (void) initialize {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

@end

@implementation MainClass(OtherCategory)

+ (void) load {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

+ (void) initialize {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

@end

/******* Implementation *******/
@implementation MainClass

+ (void) load {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

+ (void) initialize {
NSLog(@"%@ %s", [self class], __FUNCTION__);
}

@end

import 之后

MainClass +[MainClass(OtherCategory) initialize]
MainClass +[MainClass load]
MainClass +[MainClass(Category) load]
MainClass +[MainClass(OtherCategory) load]

先执行类自身的实现,再执行类别(Category)中的实现

不需要调用[super load] 和 [super initialize]

1
2
3
4
5
6
7
8
9
+ (void)initialize {
//do initialization thing
[super initialize];
}

+ (void) load {
//do some loading things
[super load];
}

这样写是多余的

开发过程中需要注意

在Objective-C中,运行时会自动调用每个类的两个方法。+load会在类初始加载时调用,+initialize会在第一次调用类的类方法或实例方法之前被调用。这两个方法是可选的,且只有在实现了它们时才会被调用。由于method swizzling会影响到类的全局状态,因此要尽量避免在并发处理中出现竞争的情况。+load能保证在类的初始化过程中被加载,并保证这种改变应用级别的行为的一致性。相比之下,+initialize在其执行时不提供这种保证–事实上,如果在应用中没为给这个类发送消息,则它可能永远不会被调用

参考链接:
Objective C类方法load和initialize的区别

12
Hello Li

Hello Li

14 日志
6 分类
5 标签
GitHub
© 2018 Hello Li
由 Hexo 强力驱动
主题 - NexT.Pisces