版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
【移动应用开发技术】如何实现一个手帐APP开发
前段时间对手帐类app的实现细节非常感兴趣,遂萌生了想自己实现一个最小化的可行性产品。当然啦~既然是MVP模式下的产品,所以只实现了「功能」,但是在一些自己特别想要去「抄袭」的地方也下了一点功夫去追求UI的表现。前段时间对手帐类app的实现细节非常感兴趣,遂萌生了想自己实现一个最小化的可行性产品。当然啦~既然是MVP模式下的产品,所以只实现了「功能」,但是在一些自己特别想要去「抄袭」的地方也下了一点功夫去追求UI的表现。前言小时候,我是一个手抄报爱好者,四年级的时候班里组织了一个手抄报比赛,老师要求每位同学利用周末的时间做一份手抄报进行评比,主题自选。到现在我印象还非常深刻的是,我想了一个中午都不知道要选什么主题,在白纸上画了一些东西后又全都擦掉了,弄脏了好几张纸,最后画出了一个地球,思路就慢慢打开了。到了周一交给老师的时候,我不敢第一个交,我排在了队伍的最后。老师接到我的手抄报后,居然说:“来来来,你们来看看什么叫手抄报”,我当时的心率达到了极高点,脸又红又烫,站在老师身边站也不是走也不是,尴尬的笑着,但内心却极度自豪。到了初中,班主任也让大家利用周末的时间去做了一个手抄报,因为在小学的时候有了一点经验,再加上到了初中那会儿基本上使用计算机来辅助完成各种任务也都铺开了,我就寻思着能不能再做些创新。当时柯达传出了倒闭的消息,这相当于是一代人的记忆吧~有时候我会跑到老房子里翻到各种胶卷,在阳光的照射下看着映射出的反射图像。结合这个事件,我就想到了利用「胶卷」风格的来阐述对保护鸟类的主题,从网上下载了一些各种鸟类的图片,自己加工一下,终于把手抄报做好了交给老师。当交给老师的那一刻,老师愉悦的笑了,并拿着我的手抄报在讲台上给同学们展示,“大家看下,做的还不错吧~嗯,挺好看!”。高考完的那个暑假,《南国都市报》组织了一次中小学生手抄报大赛,当时我用堂弟的身份参加这个大赛,拿了三等奖,奖品是一张创新书店500元的购书卡。以上就是我对手抄报或者说类似于手帐的这种手工画的经历了,我特别喜欢这种讲述一个故事的方式,可以很好的把我想要表达的东西通过一些文字、图片和画的方式展现出来。所以,当出现了手帐类app时,我迅速的下载进行使用,使用过程中确实达到了自己当初通过组织一些元素和文字来讲述一件事的初衷。前段时间突发奇想,如果我能自己做一个手帐,顺便去探究实现一个手帐app中需要注意的问题,那该多好啊!设计首先,我把AppStore中「手帐」关键词下的搜索排名前10的app都进行了一番使用,总结出了一些手帐app通用点:添加文字。可旋转、放大缩小、旋转字体;添加照片。可旋转翻转、放大缩小、并具备简单或者辅助的图像修饰工具;添加贴纸。使用一些绘制好的贴纸,操作与「添加照片」差不多;模版。提供一套模版,用户可以在这个模版规定好的区域进行内容添加;提供无限长或宽的画布。基本上这些手帐app的功能就是这么多了,因为本着MVP的思路去做这个项目,所以也就没有做到高保真的设计,直接抄了一个比较简洁的手帐app设计。技术栈确定好了自己要实现的大概需要做的功能点后,就需要开始去选择技术栈,因为要做的毕竟是MVP产品而不是demo,我对demo的理解是「实现某个功能点」,对MVP产品的理解是「某个阶段下的完整可用的产品」,MVP模式下出来的东西细节出现一些问题不用太过于苛责,但整体的逻辑上一定是要完整的,不完整的逻辑可以没有,但是一旦有了就要是完整的,覆盖的逻辑路径也可以不是100%,但主逻辑一定要全覆盖。客户端iOSAPP开发技术点如下:纯原生Swift开发;网络请求=>
Alamofire,一些简单的数据直接走
NSFileManager
进行文件持久化管理;UI组件全都基于
UIKit
去做;社会化分享走系统分享,不集成其它SDK;模块上提供「贴纸」、「画笔」、「照片」和「文字」。做的过程中发现其实「照片」和「文字」本质上来说也是贴纸,省了不少事。服务端其实我对自己每新开一个sideproject都有一个硬性要求,做完后要对自己的技术水平有增长,其实「增长」这个东西很玄学,怎么定义「增长」对吧?我给自己找到了一个最简单的思路:用新的东西去完成它!因此在服务端上我就直接无脑的选择了
Vapor
进行,通过Swift去写服务端这是我之前一直想做但找不到时机去做的事情,借此机会就上车了。至于为什么不是选
Perfect,其实我个人没有去动手实践过,只是听大佬们说
Vapor
的API风格比较
Swifty
一些。在第一期的MVP中对服务端的依赖不大,所以目前的架构比较简单,达到能用即可就完事了~关于
Vapor
的一些使用细节,可以在我的这篇文章中进行查看,本文将不再细述
Vapor
使用细节。实现手势对于手帐来说,最核心的一个就是「贴纸」。如何把贴纸从存储中拉出来放到画布上,这一步解决了,后续大部分内容也都解决了。首先,我们需要明确一点,在这个项目中,「画布」本身也是个
UIView,把「贴纸」添加到画布上,实质上就是把
UIImageView
给
addSubview
到
UIView
上。其次,手帐中追求的是对素材的控制,可旋转放大是基本操作,而且前文也说过了,我们几乎可以把「照片」和「文字」都认为是对「贴纸」的继承,所以这就抽离出了「贴纸」本身是所以可提供交互组件的基类。手帐类app对贴纸进行多手势操作的流畅性是决定用户留存率很大的一个因素。因此,我们再抽离一下手帐「贴纸」,把基础手势操作都移到更高一层的父类中去,贴纸中留下业务逻辑。手势操作核心代码逻辑如下://
pinchGesture
缩放手势//
缩放的方法(文件私有)。
gesture手势
:UI缩放手势识别器@objcfileprivate
func
pinchImage(gesture:
UIPinchGestureRecognizer)
{
//
当前手势
状态
改变中
if
gesture.state
==
.changed
{
//
当前矩阵2D变换
缩放通过(手势缩放的参数)
transform
=
transform.scaledBy(x:
gesture.scale,
y:
gesture.scale)
//
要复原到1(原尺寸),不要叠加放大
gesture.scale
=
1
}}//
rotateGesture
旋转手势//
旋转的方法(文件私有)。
gesture手势
:UI旋转手势识别器@objcfileprivate
func
rotateImage(gesture:
UIRotationGestureRecognizer)
{
if
gesture.state
==
.changed
{
transform
=
transform.rotated(by:
gesture.rotation)
//
0为弧度制(要跟角度转换)
gesture.rotation
=
0
}}//
panGesture
拖拽/平移手势//
平移的方法(文件私有)。
gesture手势
:UI平移手势识别器@objcfileprivate
func
panImage(gesture:
UIPanGestureRecognizer)
{
if
gesture.state
==
.changed
{
//
坐标转换至父视图坐标
let
gesturePosition
=
gesture.translation(in:
superview)
//
用移动距离与原位置坐标计算。
gesturePosition.x
已经带正负了
center
=
CGPoint(x:
center.x
+
gesturePosition.x,
y:
center.y
+
gesturePosition.y)
//
.zero
为
CGPoint(x:
0,
y:
0)的简写,
位置坐标回0
gesture.setTranslation(.zero,
in:
superview)
}}//
双击动作(UI点击手势识别器)@objcfileprivate
func
doubleTapGesture(tap:
UITapGestureRecognizer)
{
//
状态
双击结束后
if
tap.state
==
.ended
{
//
翻转
90度
let
ratation
=
CGFloat(Double.pi
/
2.0)
//
变换
旋转角度
=
之前的旋转角度
+
旋转
transform
=
CGAffineTransform(rotationAngle:
previousRotation
+
ratation)
previousRotation
+=
ratation
}}实现的效果下图所示:使用
UICollectionView
作为贴纸容器,通过闭包把点击事件对应索引映射的icon图片实例化为贴纸对象传递给父视图:collectionView.cellSelected
=
{
cellIndex
in
let
stickerImage
=
UIImage(named:
collectionView.iconTitle
+
"\(cellIndex)")
let
sticker
=
UNStickerView()
sticker.width
=
100
sticker.height
=
100
sticker.imgViewModel
=
UNStickerView.ImageStickerViewModel(image:
stickerImage!)
self.sticker?(sticker)}在父视图中通过实现闭包接收贴纸对象,这样就完成了「贴纸」到「画布」的全流程。stickerComponentView.sticker
=
{
$0.viewDelegate
=
self
//
父视图居中
$0.center
=
self.view.center
$0.tag
=
self.stickerTag
self.stickerTag
+=
1
self.view.addSubview($0)
//
添加到贴纸集合中
self.stickerViews.append($0)}「照片」和「文字」手帐编辑页面的底部工具栏之前没做好设计,按道理来说,应该直接上一个
UITabBar
即可完事,但最终也使用了
UICollectionView
完成。读取设备照片操作比较简单,不需要自定义相册,所以通过系统的
UIImagePicker
完成,对自定义相册感兴趣的同学可以看我的这篇文章。顶部工具栏的代码细节如下所示://
底部的点击事件collectionView.cellSelected
=
{
cellIndex
inswitch
cellIndex
{
//
背景
case
0:
self.stickerComponentView.isHidden
=
true
brushView.isHidden
=
true
self.bgImageView.image
=
brushView.drawImage()
self.present(self.colorBottomView,
animated:
true,
completion:
nil)
//
贴纸
case
1:
brushView.isHidden
=
true
self.bgImageView.image
=
brushView.drawImage()
self.stickerComponentView.isHidden
=
false
UIView.animate(withDuration:
0.25,
animations:
{
self.stickerComponentView.bottom
=
self.bottomCollectionView!.y
})
//
文字
case
2:
self.stickerComponentView.isHidden
=
true
brushView.isHidden
=
true
self.bgImageView.image
=
brushView.drawImage()
let
vc
=
UNTextViewController()
self.present(vc,
animated:
true,
completion:
nil)
plateHandler
=
{
viewModel
in
let
stickerLabel
=
UNStickerView(frame:
CGRect(x:
150,
y:
150,
width:
100,
height:
100))
self.view.addSubview(stickerLabel)
stickerLabel.textViewModel
=
viewModel
self.stickerViews.append(stickerLabel)
}
//
照片
case
3:
self.stickerComponentView.isHidden
=
true
brushView.isHidden
=
true
self.bgImageView.image
=
brushView.drawImage()
self.imagePicker.delegate
=
self
self.imagePicker.sourceType
=
.photoLibrary
self.imagePicker.allowsEditing
=
true
self.present(self.imagePicker,
animated:
true,
completion:
nil)
//
画笔
case
4:
self.stickerComponentView.isHidden
=
true
brushView.isHidden
=
false
self.bgImageView.image
=
nil
self.view.bringSubviewToFront(brushView)
default:
break}底部工具栏的每一个模块都是一个
UIView,这部分做的也不太好,最佳的做法应该是基于
UIWindow
或者
UIViewController
做一个「工具容器」作为各个模块UI内容元素的容器,通过这种做法就可以免去在底部工具栏的点击事件回调中写这么多的视图显示/隐藏的状态代码。关注「照片」部分的代码块,实现
UIImagePickerControllerDelegate
协议后的方法为:extension
UNContentViewController:
UIImagePickerControllerDelegate
{
///
从图片选择器中获取选择到的图片
func
imagePickerController(_
picker:
UIImagePickerController,
didFinishPickingMediaWithInfo
info:
[UIImagePickerController.InfoKey
:
Any])
{
//
获取到编辑后的图片
let
image
=
info[UIImagePickerController.InfoKey.editedImage]
as?
UIImage
if
image
!=
nil
{
let
wh
=
image!.size.width
/
image!.size.height
//
初始化贴纸
let
sticker
=
UNStickerView(frame:
CGRect(x:
150,
y:
150,
width:
100,
height:
100
*
wh))
//
添加视图
self.view.addSubview(sticker)
sticker.imgViewModel
=
UNStickerView.ImageStickerViewModel(image:
image!)
//
添加到贴纸集合中
self.stickerViews.append(sticker)
picker.dismiss(animated:
true,
completion:
nil)
}
}}文字文字模块暴露给父视图也是一个实例化后的贴纸对象,不过在文字VC里需要对文字进行颜色、字体和字号的选择。做完了才发现其实因为贴纸是可以通过手势进行放大和缩小的,没必要做字号的选择其中比较费劲的是对文字颜色的选择,刚开始我想的直接上RGB调色就算了,后来想到如果直接通过RGB有三个通道,调起色来非常的难受。想到之前在做《疯狂弹球》这个游戏时使用的HSB颜色模式,做一个圆盘颜色选择器,后来在思考实现细节的过程中了这么EF写的这个库
EFColorPicker,非常好用,改了改UI后直接拿来用了,感谢EF!「气泡视图」的本身是个
UIViewController,但是需要对其几个属性进行设置。其实现流程比较流程化,比较好的做法是封装一下,把这些模版化的代码变成一个「气泡视图」类供业务方使用,但因为时间关系就一直在copy,核心代码如下:///
文字大小气泡private
var
sizeBottomView:
UNBottomSizeViewController
{
get
{
let
sizePopover
=
UNBottomSizeViewController()
sizePopover.size
=
self.textView.font?.pointSize
sizePopover.preferredContentSize
=
CGSize(width:
200,
height:
100)
sizePopover.modalPresentationStyle
=
.popover
let
sizePopoverPVC
=
sizePopover.popoverPresentationController
sizePopoverPVC?.sourceView
=
self.bottomCollectionView
sizePopoverPVC?.sourceRect
=
CGRect(x:
bottomCollectionView!.cellCenterXs[1],
y:
0,
width:
0,
height:
0)
sizePopoverPVC?.permittedArrowDirections
=
.down
sizePopoverPVC?.delegate
=
self
sizePopoverPVC?.backgroundColor
=
.white
sizePopover.sizeChange
=
{
size
in
self.textView.font
=
UIFont(name:
self.textView.font!.familyName,
size:
size)
}
return
sizePopover
}}在需要弹出该气泡视图的地方通过
present
即可调用:collectionView.cellSelected
=
{
cellIndex
in
switch
cellIndex
{
case
0:
self.present(self.fontBottomView,
animated:
true,
completion:
nil)
case
1:
self.present(self.sizeBottomView,
animated:
true,
completion:
nil)
case
2:
self.present(self.colorBottomView,
animated:
true,
completion:
nil)
default:
break
}}画笔之前在滴滴实习时,写过一个关于画笔的组件(居然已经两年前了...),但是这个画笔是基于
drawRect:
方法去做的,对于内存十分不友好,一直画下去,内存就会一直涨,这回采用了
CAShapeLayer
重写了一个,效果还不错。关于画笔的撤回之前基于
drawRect:
的方式去做就会非常简单,每一次的撤回相当于重绘一次,把被撤回的线从绘制点数组中
remove
掉就好了,但基于
CAShapeLayer
实现不太一样,因为其每一笔都是直接生成在
layer
中了,如果需要撤回就得把当前重新生成
layer。所以最后我的做法是每画一笔都去生成一张图片保存到数组中,当执行撤回操作时,就把撤回数组中的最后一个元素替换当前正在的绘制画布内容,并从撤回数组中移除这个元素。有了撤回,那也要把重做给上了。重做的就是防止撤回,做法跟撤回类似。再创建一个重做数组,把每次从撤回数组中移除掉的图片都
append
到重做数组中即可。以下为撤回重做的核心代码://
undo
撤回@objcprivate
func
undo()
{
//
undoDatas
可撤回集合
数量
guard
undoDatas.count
!=
0
else
{
return
}
//
如果是撤回集合中只有
1
个数据,则说明撤回后为空
if
undoDatas.count
==
1
{
//
重做
redo
append
添加
redoDatas.append(undoDatas.last!)
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2024年度数据分析和处理合同
- 2024年度文化艺术节组织服务合同
- 2024年度水果连锁加盟店苹果采购合同(零售业)
- 2024年度广告投放合作保密合同
- 连体服市场发展现状调查及供需格局分析预测报告
- 2024年度建筑工地塔吊司机劳动合同
- 2024年度特许经营合同:某连锁品牌授权某地区投资者开设加盟店
- 补内胎用全套工具市场发展预测和趋势分析
- 2024年度建筑工程施工合同标的物为一个住宅小区的建设
- 2024年度互联网信息服务合同:互联网企业与用户之间的信息服务协议
- 小学教育课件教案国家财政与税收认识国家财政的来源与用途
- 大型集团公司企业内部控制制度和流程汇编
- 关于开展返乡农民工服务工作的实施方案
- 二手车市场项目实施方案
- 植物生产类专业职业生涯规划书
- 中学生纪律教育主题班会课件
- 法院档案搬迁服务投标方案(技术标)
- 产品供货方案、售后服务方案
- 人教版 六级上册数学 六单元 百分数一(省级作业设计大赛作品)
- 神经外科教学大纲
- 《企业财务会计》 课件 学习情境10 所有者权益业务核算
评论
0/150
提交评论