这次重构的主要目的就是留下一个方便阅读的代码给学弟学妹们学习,也在这个重构的过程中重新实现了部分功能,也解决了一些非常影响用户体验的bug,下面就是涉及到的几个方面
包体积减小
这里主要涉及到的就是无用图片资源和无用代码的清理
无用图片
基本上删除无用图片都是用的这个LSUnusedResources,筛选条件可以自己添加,因为图片命名比较规则,我就选择了默认的条件
无用代码
因为项目的文件不是很多,所以使用的是XcodeProjectArrangementTool它会检测工程文件中没有使用的类,然后我们可以一个个去工程中排除它
编译选项
Valid Architectures
设置编译生成的 ipa 包所支持的架构,不支持32位以及 iOS8 ,可去掉 armv7及之前的架构 ,减小生成的 ipa 包
Strip Link Product 和 Deployment Postprocessing
Strip Linked Product 默认为 Yes,Deployment Postprocessing 默认为 No,Strip Linked Product 在 DeploymentPostprocessing 设置为 YES 的时候才生效。当Strip Linked Product设为YES的时候,ipa会去除掉symbol符号,运行 App 时断点不会中断,建议在release下都设置为Yes
这两项是需要修改的编译选项,其他的我都是使用的默认值
其他未涉及方向
- 图片资源还可以通过转化Webp格式的图片进行压缩
- Mach—O文件的各种神奇魔法(需要非常了解iOS编译和编译原理相关知识)
UI
食品详情页
这是没有修改之前的食品详情页,主要问题就是这个内容View和内容View中的TableView存在滑动冲突,不能同时滚动,而且内部代码混杂,在滚动时会动态调整UI,造成卡顿感
首先我把这个内容View单独封装出来,保留一个传递数据的接口
class DishContentView: UIView {
//MARK: -公有属性
public var buttonBlock:(() -> ())?
public var scrollBlock:((Bool) -> ())?
public var tableScroll = false
//MARK: -私有属性
private var dish = Dish()
private var caloris:Int = 0
private var data = [Ingredient]()
.....
public func updateUI(with data: Dish) {
....
}
}
其次我们需要修改滚动的逻辑,我们将ScrollView放在底部,并且重写它的一个方法,使TableView和ScrollView能够同时接收到滚动手势
class bottomScrollView: UIScrollView, UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
然后我们还需要去设置他们的滚动逻辑,当底部ScrollView没有滚动到指定位置时,TableView是无法进行滚动的,从而给予用户一个流程的滑动体验
extension DishContentView: UITableViewDelegate, UITableViewDataSource, UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !tableScroll {
scrollView.contentOffset.y = 0
} else {
if scrollView.contentOffset.y <= 0 {
tableScroll = false
scrollBlock?(true)
}
}
}
}
extension DishDetailViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let maxOffset: CGFloat = 250
if !viewScroll {
scrollView.contentOffset.y = maxOffset
} else {
if scrollView.contentOffset.y >= maxOffset {
scrollView.contentOffset.y = maxOffset
viewScroll = false
dishView.tableScroll = true
}
}
//当table不够长时保证能够向上滑回
if !viewScroll && dishView.tableScroll && dishView.ingredientsTableView.contentOffset.y == 0{
viewScroll = true
}
}
}
最终效果如下:
个人信息修改界面
当时看见这个UI的时候,我只能说非常佩服那个实现它的同学,属实是非常的不好看,于是我就重写了一个UIPickerView,由于它有几个类型,于是我在传递数据的接口上增加了一个类型参数,根据参数使用不同的数据呈现选择器。这其中的核心问题就是为了使日期选择器不会选择出不存在的日期加的一些判断。
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch viewType {
case .date:
let year = years[pickerView.selectedRow(inComponent: 0)]
let month = months[pickerView.selectedRow(inComponent: 1)]
let day = days[pickerView.selectedRow(inComponent: 2)]
if month == 2 {
if (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0) {
if day > 28 {
pickerView.selectRow(27, inComponent: 2, animated: true)
}
} else {
if day > 29 {
pickerView.selectRow(28, inComponent: 2, animated: true)
}
}
} else {
if month == 2 || month == 4 || month == 6 || month == 9 {
if day > 30 {
pickerView.selectRow(29, inComponent: 2, animated: true)
}
}
}
default:
break
}
}
之前这个界面还有数据持久化,但是没有传入上一个返回页面,所以我用沙盒+plist重新实现了这个功能,代码如下:
func archivImage(image: UIImage, type: String) -> String {
if let imageData = image.jpegData(compressionQuality: 400) as NSData? {
let fullPath = NSHomeDirectory().appending("/Documents/").appending(type)
imageData.write(toFile: fullPath, atomically: true)
return fullPath
}
return ""
}
func archiveData(data: MineModel) {
let dic = NSDictionary(dictionary: data.toJSON()!)
let path = NSHomeDirectory().appending("/Documents/user.plist")
let arr = NSMutableArray()
arr.add(dic)
arr.write(toFile: path, atomically: true)
}
func getLocalData() -> MineModel {
let path = NSHomeDirectory().appending("/Documents/user.plist")
if let data = NSArray(contentsOfFile: path) {
let dic = data[0] as! NSDictionary
let model = MineModel.deserialize(from: dic, designatedPath: "")!
return model
} else {
return MineModel(backgroundImage: "", userImage: "", userName: "去冰无糖", sex: "女", weight: "52", height: "168", birthday: "2000-01-01")
}
}
最终呈现出来的效果如下:
动画
动画这方面是之前在用一些App的时候,感觉很有意思,于是特意学习了一些动画效果来实现
Cell动画
这个动画的效果比较简单,当个也可以设置从不同的方向进来也是可以的
func cellAnimation(cell: UICollectionViewCell, interval: TimeInterval) {
UIView.animate(withDuration: 0.0) {
cell.transform = CGAffineTransform(translationX: CFWidth, y: 0.0)
}
delay(by: interval) {
UIView.animate(withDuration: interval + 0.1) {
cell.transform = CGAffineTransform.identity
}
}
}
func delay(by delay: TimeInterval, code block: @escaping () -> Void) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(delay * Double(NSEC_PER_SEC)) / Double(NSEC_PER_SEC),
execute: block)
}
添加动画
这个动画就比较有意思了,就是美团App里面的那种购物车效果动画,关键点就在于利用贝塞尔曲线来绘制曲线轨迹,同时我还加了一个旋转的效果,显的有点魔性哈哈
代码如下:
class AddAnimation: NSObject {
var finishBlock: ((Bool) -> Void)?
private var layer: CALayer?
func start(view: UIView, rect: CGRect, finishPoint: CGPoint, finishBlock: @escaping ((Bool) -> Void)) {
layer = CALayer()
layer?.contents = view.layer.contents
layer?.contentsGravity = CALayerContentsGravity.resize
layer?.frame = rect
let window: UIView = view.window!
window.layer.addSublayer(layer!)
let path = UIBezierPath()
path.move(to: (layer?.position)!)
//通过两点来绘制曲线
path.addQuadCurve(to: finishPoint, controlPoint: CGPoint(x: window.frame.size.width / 2, y: rect.origin.y - 40))
// 负责曲线运动
let pathAnimation: CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "position") // 位置的平移
pathAnimation.path = path.cgPath
// 负责旋转 rotation
let basicAnimation = CABasicAnimation(keyPath: "transform.rotation")
basicAnimation.isRemovedOnCompletion = true
basicAnimation.fromValue = NSNumber(value: 0)
basicAnimation.toValue = NSNumber(value: 3 * 2 * Double.pi) // 这里是旋转的角度
basicAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeIn)
// 组合动画
let groups: CAAnimationGroup = CAAnimationGroup()
groups.animations = [pathAnimation, basicAnimation]
groups.duration = 0.5 // 国际单位制 S
groups.fillMode = CAMediaTimingFillMode.forwards
groups.isRemovedOnCompletion = false
groups.delegate = self
layer?.add(groups, forKey: "groups")
self.finishBlock = finishBlock
}
}
extension AddAnimation: CAAnimationDelegate {
func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
if anim == layer!.animation(forKey: "groups") {
layer?.removeFromSuperlayer()
layer = nil
if finishBlock != nil {
finishBlock!(true)
}
}
}
}
其他方面
其实整个重构过程下来,我还是收获蛮大的,不管是功能的实现方面还是问题的解决能力方面。但是在这个过程中,我意识到最重要的就是要有良好的代码规范和利用Git版本控制。
- 代码规范:各种缩进什么的那都是个人习惯不同的,但是类似于命名和文件结构之类的问题,就需要尽量保持可阅读,清晰简洁。类里面的属性和方法都应该统一归类利用MARK来分隔开提高可读性
- Git:学会使用Git是团队开发的第一步,而且不仅仅可以用于团队开发,在你个人进行开发的时候,在某一个功能实现的过程中,代码重复删改最后出现无法运行,这个时候没有Git的话,可能很大部分要重新写起,可是有了Git,你只需要回滚的上一个版本中即可
重构之后的代码也已经上架AppStore了,如果有需要点击树食