【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳 APP開發(fā)_第1頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳 APP開發(fā)_第2頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳 APP開發(fā)_第3頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳 APP開發(fā)_第4頁
【移動(dòng)應(yīng)用開發(fā)技術(shù)】如何實(shí)現(xiàn)一個(gè)手帳 APP開發(fā)_第5頁
已閱讀5頁,還剩10頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(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ì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論