版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳APP開發(fā)
前段時(shí)間對(duì)手帳類app的實(shí)現(xiàn)細(xì)節(jié)非常感興趣,遂萌生了想自己實(shí)現(xiàn)一個(gè)最小化的可行性產(chǎn)品。當(dāng)然啦~既然是MVP模式下的產(chǎn)品,所以只實(shí)現(xiàn)了「功能」,但是在一些自己特別想要去「抄襲」的地方也下了一點(diǎn)功夫去追求UI的表現(xiàn)。前段時(shí)間對(duì)手帳類app的實(shí)現(xiàn)細(xì)節(jié)非常感興趣,遂萌生了想自己實(shí)現(xiàn)一個(gè)最小化的可行性產(chǎn)品。當(dāng)然啦~既然是MVP模式下的產(chǎn)品,所以只實(shí)現(xiàn)了「功能」,但是在一些自己特別想要去「抄襲」的地方也下了一點(diǎn)功夫去追求UI的表現(xiàn)。前言小時(shí)候,我是一個(gè)手抄報(bào)愛好者,四年級(jí)的時(shí)候班里組織了一個(gè)手抄報(bào)比賽,老師要求每位同學(xué)利用周末的時(shí)間做一份手抄報(bào)進(jìn)行評(píng)比,主題自選。到現(xiàn)在我印象還非常深刻的是,我想了一個(gè)中午都不知道要選什么主題,在白紙上畫了一些東西后又全都擦掉了,弄臟了好幾張紙,最后畫出了一個(gè)地球,思路就慢慢打開了。到了周一交給老師的時(shí)候,我不敢第一個(gè)交,我排在了隊(duì)伍的最后。老師接到我的手抄報(bào)后,居然說:“來來來,你們來看看什么叫手抄報(bào)”,我當(dāng)時(shí)的心率達(dá)到了極高點(diǎn),臉又紅又燙,站在老師身邊站也不是走也不是,尷尬的笑著,但內(nèi)心卻極度自豪。到了初中,班主任也讓大家利用周末的時(shí)間去做了一個(gè)手抄報(bào),因?yàn)樵谛W(xué)的時(shí)候有了一點(diǎn)經(jīng)驗(yàn),再加上到了初中那會(huì)兒基本上使用計(jì)算機(jī)來輔助完成各種任務(wù)也都鋪開了,我就尋思著能不能再做些創(chuàng)新。當(dāng)時(shí)柯達(dá)傳出了倒閉的消息,這相當(dāng)于是一代人的記憶吧~有時(shí)候我會(huì)跑到老房子里翻到各種膠卷,在陽光的照射下看著映射出的反射圖像。結(jié)合這個(gè)事件,我就想到了利用「膠卷」風(fēng)格的來闡述對(duì)保護(hù)鳥類的主題,從網(wǎng)上下載了一些各種鳥類的圖片,自己加工一下,終于把手抄報(bào)做好了交給老師。當(dāng)交給老師的那一刻,老師愉悅的笑了,并拿著我的手抄報(bào)在講臺(tái)上給同學(xué)們展示,“大家看下,做的還不錯(cuò)吧~嗯,挺好看!”。高考完的那個(gè)暑假,《南國都市報(bào)》組織了一次中小學(xué)生手抄報(bào)大賽,當(dāng)時(shí)我用堂弟的身份參加這個(gè)大賽,拿了三等獎(jiǎng),獎(jiǎng)品是一張創(chuàng)新書店500元的購書卡。以上就是我對(duì)手抄報(bào)或者說類似于手帳的這種手工畫的經(jīng)歷了,我特別喜歡這種講述一個(gè)故事的方式,可以很好的把我想要表達(dá)的東西通過一些文字、圖片和畫的方式展現(xiàn)出來。所以,當(dāng)出現(xiàn)了手帳類app時(shí),我迅速的下載進(jìn)行使用,使用過程中確實(shí)達(dá)到了自己當(dāng)初通過組織一些元素和文字來講述一件事的初衷。前段時(shí)間突發(fā)奇想,如果我能自己做一個(gè)手帳,順便去探究實(shí)現(xiàn)一個(gè)手帳app中需要注意的問題,那該多好??!設(shè)計(jì)首先,我把AppStore中「手帳」關(guān)鍵詞下的搜索排名前10的app都進(jìn)行了一番使用,總結(jié)出了一些手帳app通用點(diǎn):添加文字。可旋轉(zhuǎn)、放大縮小、旋轉(zhuǎn)字體;添加照片??尚D(zhuǎn)翻轉(zhuǎn)、放大縮小、并具備簡單或者輔助的圖像修飾工具;添加貼紙。使用一些繪制好的貼紙,操作與「添加照片」差不多;模版。提供一套模版,用戶可以在這個(gè)模版規(guī)定好的區(qū)域進(jìn)行內(nèi)容添加;提供無限長或?qū)挼漠嫴??;旧线@些手帳app的功能就是這么多了,因?yàn)楸局鳰VP的思路去做這個(gè)項(xiàng)目,所以也就沒有做到高保真的設(shè)計(jì),直接抄了一個(gè)比較簡潔的手帳app設(shè)計(jì)。技術(shù)棧確定好了自己要實(shí)現(xiàn)的大概需要做的功能點(diǎn)后,就需要開始去選擇技術(shù)棧,因?yàn)橐龅漠吘故荕VP產(chǎn)品而不是demo,我對(duì)demo的理解是「實(shí)現(xiàn)某個(gè)功能點(diǎn)」,對(duì)MVP產(chǎn)品的理解是「某個(gè)階段下的完整可用的產(chǎn)品」,MVP模式下出來的東西細(xì)節(jié)出現(xiàn)一些問題不用太過于苛責(zé),但整體的邏輯上一定是要完整的,不完整的邏輯可以沒有,但是一旦有了就要是完整的,覆蓋的邏輯路徑也可以不是100%,但主邏輯一定要全覆蓋??蛻舳薸OSAPP開發(fā)技術(shù)點(diǎn)如下:純?cè)鶶wift開發(fā);網(wǎng)絡(luò)請(qǐng)求=>
Alamofire,一些簡單的數(shù)據(jù)直接走
NSFileManager
進(jìn)行文件持久化管理;UI組件全都基于
UIKit
去做;社會(huì)化分享走系統(tǒng)分享,不集成其它SDK;模塊上提供「貼紙」、「畫筆」、「照片」和「文字」。做的過程中發(fā)現(xiàn)其實(shí)「照片」和「文字」本質(zhì)上來說也是貼紙,省了不少事。服務(wù)端其實(shí)我對(duì)自己每新開一個(gè)sideproject都有一個(gè)硬性要求,做完后要對(duì)自己的技術(shù)水平有增長,其實(shí)「增長」這個(gè)東西很玄學(xué),怎么定義「增長」對(duì)吧?我給自己找到了一個(gè)最簡單的思路:用新的東西去完成它!因此在服務(wù)端上我就直接無腦的選擇了
Vapor
進(jìn)行,通過Swift去寫服務(wù)端這是我之前一直想做但找不到時(shí)機(jī)去做的事情,借此機(jī)會(huì)就上車了。至于為什么不是選
Perfect,其實(shí)我個(gè)人沒有去動(dòng)手實(shí)踐過,只是聽大佬們說
Vapor
的API風(fēng)格比較
Swifty
一些。在第一期的MVP中對(duì)服務(wù)端的依賴不大,所以目前的架構(gòu)比較簡單,達(dá)到能用即可就完事了~關(guān)于
Vapor
的一些使用細(xì)節(jié),可以在我的這篇文章中進(jìn)行查看,本文將不再細(xì)述
Vapor
使用細(xì)節(jié)。實(shí)現(xiàn)手勢(shì)對(duì)于手帳來說,最核心的一個(gè)就是「貼紙」。如何把貼紙從存儲(chǔ)中拉出來放到畫布上,這一步解決了,后續(xù)大部分內(nèi)容也都解決了。首先,我們需要明確一點(diǎn),在這個(gè)項(xiàng)目中,「畫布」本身也是個(gè)
UIView,把「貼紙」添加到畫布上,實(shí)質(zhì)上就是把
UIImageView
給
addSubview
到
UIView
上。其次,手帳中追求的是對(duì)素材的控制,可旋轉(zhuǎn)放大是基本操作,而且前文也說過了,我們幾乎可以把「照片」和「文字」都認(rèn)為是對(duì)「貼紙」的繼承,所以這就抽離出了「貼紙」本身是所以可提供交互組件的基類。手帳類app對(duì)貼紙進(jìn)行多手勢(shì)操作的流暢性是決定用戶留存率很大的一個(gè)因素。因此,我們?cè)俪殡x一下手帳「貼紙」,把基礎(chǔ)手勢(shì)操作都移到更高一層的父類中去,貼紙中留下業(yè)務(wù)邏輯。手勢(shì)操作核心代碼邏輯如下://
pinchGesture
縮放手勢(shì)//
縮放的方法(文件私有)。
gesture手勢(shì)
:UI縮放手勢(shì)識(shí)別器@objcfileprivate
func
pinchImage(gesture:
UIPinchGestureRecognizer)
{
//
當(dāng)前手勢(shì)
狀態(tài)
改變中
if
gesture.state
==
.changed
{
//
當(dāng)前矩陣2D變換
縮放通過(手勢(shì)縮放的參數(shù))
transform
=
transform.scaledBy(x:
gesture.scale,
y:
gesture.scale)
//
要復(fù)原到1(原尺寸),不要疊加放大
gesture.scale
=
1
}}//
rotateGesture
旋轉(zhuǎn)手勢(shì)//
旋轉(zhuǎn)的方法(文件私有)。
gesture手勢(shì)
:UI旋轉(zhuǎn)手勢(shì)識(shí)別器@objcfileprivate
func
rotateImage(gesture:
UIRotationGestureRecognizer)
{
if
gesture.state
==
.changed
{
transform
=
transform.rotated(by:
gesture.rotation)
//
0為弧度制(要跟角度轉(zhuǎn)換)
gesture.rotation
=
0
}}//
panGesture
拖拽/平移手勢(shì)//
平移的方法(文件私有)。
gesture手勢(shì)
:UI平移手勢(shì)識(shí)別器@objcfileprivate
func
panImage(gesture:
UIPanGestureRecognizer)
{
if
gesture.state
==
.changed
{
//
坐標(biāo)轉(zhuǎn)換至父視圖坐標(biāo)
let
gesturePosition
=
gesture.translation(in:
superview)
//
用移動(dòng)距離與原位置坐標(biāo)計(jì)算。
gesturePosition.x
已經(jīng)帶正負(fù)了
center
=
CGPoint(x:
center.x
+
gesturePosition.x,
y:
center.y
+
gesturePosition.y)
//
.zero
為
CGPoint(x:
0,
y:
0)的簡寫,
位置坐標(biāo)回0
gesture.setTranslation(.zero,
in:
superview)
}}//
雙擊動(dòng)作(UI點(diǎn)擊手勢(shì)識(shí)別器)@objcfileprivate
func
doubleTapGesture(tap:
UITapGestureRecognizer)
{
//
狀態(tài)
雙擊結(jié)束后
if
tap.state
==
.ended
{
//
翻轉(zhuǎn)
90度
let
ratation
=
CGFloat(Double.pi
/
2.0)
//
變換
旋轉(zhuǎn)角度
=
之前的旋轉(zhuǎn)角度
+
旋轉(zhuǎn)
transform
=
CGAffineTransform(rotationAngle:
previousRotation
+
ratation)
previousRotation
+=
ratation
}}實(shí)現(xiàn)的效果下圖所示:使用
UICollectionView
作為貼紙容器,通過閉包把點(diǎn)擊事件對(duì)應(yīng)索引映射的icon圖片實(shí)例化為貼紙對(duì)象傳遞給父視圖: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)}在父視圖中通過實(shí)現(xiàn)閉包接收貼紙對(duì)象,這樣就完成了「貼紙」到「畫布」的全流程。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)}「照片」和「文字」手帳編輯頁面的底部工具欄之前沒做好設(shè)計(jì),按道理來說,應(yīng)該直接上一個(gè)
UITabBar
即可完事,但最終也使用了
UICollectionView
完成。讀取設(shè)備照片操作比較簡單,不需要自定義相冊(cè),所以通過系統(tǒng)的
UIImagePicker
完成,對(duì)自定義相冊(cè)感興趣的同學(xué)可以看我的這篇文章。頂部工具欄的代碼細(xì)節(jié)如下所示://
底部的點(diǎn)擊事件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}底部工具欄的每一個(gè)模塊都是一個(gè)
UIView,這部分做的也不太好,最佳的做法應(yīng)該是基于
UIWindow
或者
UIViewController
做一個(gè)「工具容器」作為各個(gè)模塊UI內(nèi)容元素的容器,通過這種做法就可以免去在底部工具欄的點(diǎn)擊事件回調(diào)中寫這么多的視圖顯示/隱藏的狀態(tài)代碼。關(guān)注「照片」部分的代碼塊,實(shí)現(xiàn)
UIImagePickerControllerDelegate
協(xié)議后的方法為: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)
}
}}文字文字模塊暴露給父視圖也是一個(gè)實(shí)例化后的貼紙對(duì)象,不過在文字VC里需要對(duì)文字進(jìn)行顏色、字體和字號(hào)的選擇。做完了才發(fā)現(xiàn)其實(shí)因?yàn)橘N紙是可以通過手勢(shì)進(jìn)行放大和縮小的,沒必要做字號(hào)的選擇其中比較費(fèi)勁的是對(duì)文字顏色的選擇,剛開始我想的直接上RGB調(diào)色就算了,后來想到如果直接通過RGB有三個(gè)通道,調(diào)起色來非常的難受。想到之前在做《瘋狂彈球》這個(gè)游戲時(shí)使用的HSB顏色模式,做一個(gè)圓盤顏色選擇器,后來在思考實(shí)現(xiàn)細(xì)節(jié)的過程中了這么EF寫的這個(gè)庫
EFColorPicker,非常好用,改了改UI后直接拿來用了,感謝EF!「氣泡視圖」的本身是個(gè)
UIViewController,但是需要對(duì)其幾個(gè)屬性進(jìn)行設(shè)置。其實(shí)現(xiàn)流程比較流程化,比較好的做法是封裝一下,把這些模版化的代碼變成一個(gè)「氣泡視圖」類供業(yè)務(wù)方使用,但因?yàn)闀r(shí)間關(guān)系就一直在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
即可調(diào)用: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
}}畫筆之前在滴滴實(shí)習(xí)時(shí),寫過一個(gè)關(guān)于畫筆的組件(居然已經(jīng)兩年前了...),但是這個(gè)畫筆是基于
drawRect:
方法去做的,對(duì)于內(nèi)存十分不友好,一直畫下去,內(nèi)存就會(huì)一直漲,這回采用了
CAShapeLayer
重寫了一個(gè),效果還不錯(cuò)。關(guān)于畫筆的撤回之前基于
drawRect:
的方式去做就會(huì)非常簡單,每一次的撤回相當(dāng)于重繪一次,把被撤回的線從繪制點(diǎn)數(shù)組中
remove
掉就好了,但基于
CAShapeLayer
實(shí)現(xiàn)不太一樣,因?yàn)槠涿恳还P都是直接生成在
layer
中了,如果需要撤回就得把當(dāng)前重新生成
layer。所以最后我的做法是每畫一筆都去生成一張圖片保存到數(shù)組中,當(dāng)執(zhí)行撤回操作時(shí),就把撤回?cái)?shù)組中的最后一個(gè)元素替換當(dāng)前正在的繪制畫布內(nèi)容,并從撤回?cái)?shù)組中移除這個(gè)元素。有了撤回,那也要把重做給上了。重做的就是防止撤回,做法跟撤回類似。再創(chuàng)建一個(gè)重做數(shù)組,把每次從撤回?cái)?shù)組中移除掉的圖片都
append
到重做數(shù)組中即可。以下為撤回重做的核心代碼://
undo
撤回@objcprivate
func
undo()
{
//
undoDatas
可撤回集合
數(shù)量
guard
undoDatas.count
!=
0
else
{
return
}
//
如果是撤回集合中只有
1
個(gè)數(shù)據(jù),則說明撤回后為空
if
undoDatas.count
==
1
{
//
重做
redo
append
添加
redoDatas.append(undoDatas.last!)
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 如何做一道簡單的甜點(diǎn)
- 藍(lán)天白云的成因和氣象變化
- 物聯(lián)網(wǎng)在車聯(lián)網(wǎng)中的具體應(yīng)用分析
- 高中學(xué)生的學(xué)業(yè)煩惱與解決
- 平面設(shè)計(jì)圖案題目
- 科學(xué)競(jìng)技挑戰(zhàn)科學(xué)知識(shí)極限
- 安全防護(hù):如何防止火災(zāi)
- 小學(xué)節(jié)約資源小測(cè)驗(yàn)
- 如何培養(yǎng)學(xué)生的判斷力和批判思維能力
- 初中科學(xué)創(chuàng)新模擬考試試題
- 2024全球智能家居市場(chǎng)深度研究報(bào)告
- 2024年高級(jí)農(nóng)業(yè)經(jīng)理人(三級(jí))技能鑒定考試題庫(含答案)
- 自習(xí)室創(chuàng)業(yè)項(xiàng)目計(jì)劃書
- §9.10多面體歐拉定理的發(fā)現(xiàn)
- 適合打印的 五十音圖表
- 鋼結(jié)構(gòu)竣工資料范本模板(共155頁)
- 完整版用友NC操作手冊(cè)輕松學(xué)財(cái)務(wù)軟件
- 氣旋與反氣旋教學(xué)課件
- 職業(yè)健康體檢質(zhì)量管理手冊(cè)(共58頁)
- Multisim電子技術(shù)基礎(chǔ)仿真實(shí)驗(yàn)二菜單欄學(xué)習(xí)教案
評(píng)論
0/150
提交評(píng)論