iOS的几个特效实现思路

rainbow
最近看一个app的源码,发现基本没有用第三方的开源组件,但是特效也做得不错,总结一下实现的思路

简单的抽屉效果

效果如图:
抽屉效果

这种抽屉效果很常见,开源组件也很多。但是一般开源组件都对Controller的结构有要求,有时候不是很方便。

原理主要是:主页面加侧边栏。当弹出侧边栏时,设置主页面的x为一个负数;当收回侧边栏时,将主页面的x设置为0。再加上一些动画和手势就可以了。

初始化侧边栏

1
2
3
4
5
6
7
8
9
10
11
12
13
func addSidePanelController() {
if (sidePanelController == nil) {
sidePanelController = UIStoryboard.deviceListPanelController() // 初始化侧边栏的controller,用storyboard或者代码不是核心
view.insertSubview(sidePanelController!.view, atIndex: 0) // 添加侧边栏view
sidePanelController!.view.frame = CGRectMake(
expandedOffset,
topLayoutGuide.length,
view.bounds.width - expandedOffset,
view.bounds.height - topLayoutGuide.length) // 设置侧边栏frame
addChildViewController(sidePanelController!) // 添加侧边栏controller为子controller
sidePanelController!.didMoveToParentViewController(self)
}
}

处理弹出和收回

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
func animatePanel(shouldExpand shouldExpand: Bool) {

// 侧边栏展开时,主页面的x设置为一个负值
if (shouldExpand) {
deviceListExpanded = true
animateCenterPanelXPosition(targetPosition:
-CGRectGetWidth(mainTabController.view.frame) + expandedOffset) { _ in
self.mainTabController.childViewEnabled = false // 弹出侧边栏,主页面不响应用户点击
}
} else {

// 侧边栏收回时,主页面的x设置为0
animateCenterPanelXPosition(targetPosition: 0) { _ in
self.deviceListExpanded = false
self.sidePanelController!.view.removeFromSuperview()
self.sidePanelController = nil
self.mainTabController.childViewEnabled = true // 收回侧边栏,主页面可响应用户点击
}
}
}

func animateCenterPanelXPosition(targetPosition targetPosition: CGFloat, completion: ((Bool) -> Void)! = nil) {

UIView.animateWithDuration(0.5,
delay: 0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0,
options: .CurveEaseInOut,
animations: {
self.mainTabController.view.frame.origin.x = targetPosition // 这行是核心
}, completion: completion)
}

UITableView行展开效果

效果如图:
行展开效果

这个效果的原理也很简单,不展开的时候row height是某个值,展开后是另一个值,在select row的时候做一个动画就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if (indexPath == tableView.indexPathForSelectedRow) {
return Compatibility.ProductCellHeight + 87
} else {
return Compatibility.ProductCellHeight
}
}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
// recalculate row height
tableView.beginUpdates()
tableView.endUpdates()
}

override func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
if indexPath == tableView.indexPathForSelectedRow {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
tableView.beginUpdates()
tableView.endUpdates()
return nil
}
return indexPath
}

隐藏static cell的某个section

总的来说,对于设置为static cell的UITableView,要想动态地隐藏部分section,通过heightForXXX没有用。即使设置成0,还是会出现在界面上,只是会挤压在一起

正确的做法是,用numberOfRows方法隐藏row,用titleForHeader和titleForFooter方法隐藏header和footer

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
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

if (section == monoPickerSection && hideMonoPicker) {
return 0
}

return super.tableView(tableView, numberOfRowsInSection: section)
}

override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {

if (section == monoPickerSection && hideMonoPicker) {
return nil
}

return super.tableView(tableView, titleForHeaderInSection: section)
}

override func tableView(tableView: UITableView, titleForFooterInSection section: Int) -> String? {

if (section == monoPickerSection && hideMonoPicker) {
return nil
}

return super.tableView(tableView, titleForFooterInSection: section)
}