#Task-11 照片編輯App- PhotoEditor (02- 旋轉、文字&貼圖)

這次的作業是要做一個可以編輯照片的 App,會分成 4 篇來介紹,第二篇是照片旋轉、加入文字&貼圖

💡 第一篇在這裡,含完整畫面及操作影片

決定好要編輯的照片之後,我們就正式進入編輯畫面吧!

因為照片調色、套用濾鏡、更改尺寸的功能都有獨立的 view controller,所以主畫面我直接放上可以加入文字&貼圖,以及照片旋轉、還原、儲存、返回拍照/選照片頁的按鈕

⭐️ 照片旋轉

裝照片的 view 有 2 層,最外層的 Container View 最後儲存照片會使用到,還有文字跟貼圖也會放在這層;mirror view 是水平翻轉照片用,向左旋轉 90 度則直接作用在顯示照片的 image view上

註:如果沒有 mirror view,把水平翻轉跟向左旋轉都直接寫在 image view 上的話,他們會變成個別作用,只能水平翻轉或是向左旋轉,沒辦法又水平翻轉又向左旋轉哦!

1. 為了判斷照片的狀態,宣告變數初始值

//儲存圖片水平旋轉狀態的property
var mirrorNum = 1
//儲存圖片向左旋轉次數的property
var turnNum = 1

2. 水平旋轉的 IBAction function

@IBAction func mirrorRotate(_ sender: Any) {
//mirrorNum除2餘1的時候,表示圖片未水平翻轉過,做水平翻轉
if mirrorNum % 2 == 1{
mirrorView.transform = CGAffineTransform(scaleX: -1, y: 1)

//mirrorNum除2餘0的時候,表示圖片已經翻轉過,轉回圖片原本的方向
}else if mirrorNum % 2 == 0{
mirrorView.transform = CGAffineTransform(scaleX: 1, y: 1)
}
//每做完一次翻轉mirrorNum就+1,以判斷圖片現在的狀態
mirrorNum += 1

}

3. 向左旋轉 90 度的 IBAction function

須先判斷照片是否水平翻轉,再決定如何向左旋轉,因為水平翻轉後的座標軸也跟著改變,原本的向左旋轉就會變成向右旋轉😅

@IBAction func turnLeft(_ sender: Any) {    //mirrorNum除2餘1,表示照片沒有水平翻轉過,仍是原本的方向
if mirrorNum % 2 == 1{
//判斷照片旋轉幾次,轉3次會回到原本方向,所以turnNum等於1~3的時候,要向左旋轉
if turnNum < 4{
//旋轉角度乘以-1代表逆時針旋轉
imageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2 * -1 * CGFloat(turnNum))
//turnNum等於4表示要轉回原本方向了,讓旋轉值值歸0
}else if turnNum == 4{
imageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2 * 0)
//讓turnNum等於0是因為最後會再+1,才會回到初始值
turnNum = 0
}
//mirrorNum除2餘0,表示照片水平翻轉過,旋轉方向需改變
}else if mirrorNum % 2 == 0{
if turnNum < 4{
//以照片視角來說,x軸顛倒,所以向右轉才是左轉
imageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2 * 1 * CGFloat(turnNum))
}else if turnNum == 4{
imageView.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 2 * 0)
turnNum = 0
}
}
turnNum += 1
}

⭐️ 加入文字

  • 點文字符號在 image view 中間加入 text field
  • 用 slider 調整字的大小
  • 點調色盤跳出 UIColorPickerViewController 選文字顏色
  • UIPanGestureRecognizer 拖曳手勢移動文字位置

1. 宣告儲存文字的變數

var text = UITextField()

2. 加入文字的 IBAction function

@IBAction func addFont(_ sender: Any) {
//先判斷containerView中是否有文字,沒有的話才加入文字,有的話就不執行動作
if containerView.subviews.contains(text) == false{
//設定text field的相關屬性
text.placeholder = "輸入文字" //提示字
text.borderStyle = .none //外框風格
text.textAlignment = .center //文字對齊方式
text.font = UIFont.systemFont(ofSize: 20) //文字預設大小
text.textColor = .black //文字顏色
text.frame.size = CGSize(width: 200, height: 100) //text field外框大小
text.allowsEditingTextAttributes = true //設定可編輯文字屬性(選取字之後會顯示可執行的動作)
text.center = imageView.center //讓文字出現在image view的中間

//加入text field到containerView中
containerView.addSubview(text)
}

}

3. 設定 UIColorPickerViewControllerDelegate (extension寫在class外面),使用 colorPickerViewControllerDidFinish(_:) function 來變更文字顏色

註:原本是 colorPickerViewControllerDidSelectColor(_:) ,但他要被官方淘汰了,請改用 colorPickerViewControllerDidFinish(_:) 唷!

extension imageEditViewController: UIColorPickerViewControllerDelegate{
func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) {
//讓文字顏色等於所選擇的顏色
text.textColor = viewController.selectedColor
}
}

4.1. 設定 UIPanGestureRecognizer 拖曳手勢 objc function

@objc func moveFont(_ sender: UIPanGestureRecognizer){
//宣告property儲存手拖曳時在containerView裡的位置點
let point = sender.location(in: self.containerView)
//設定中心點為拖曳點
text.center = point
}

4.2. 在 viewDidLoad 中設定 UIPanGestureRecognizer 的相關屬性

override func viewDidLoad() {
super.viewDidLoad()
imageView.image = selectItem

//設定拖曳手勢,用在移動字
let panFontGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveFont(_:)))
//設定單點觸控拖曳
panFontGestureRecognizer.minimumNumberOfTouches = 1
panFontGestureRecognizer.maximumNumberOfTouches = 1
//text field要開啟isUserInteractionEnabled,觸控才有反應
text.isUserInteractionEnabled = true
//將拖曳手勢加到text field上
text.addGestureRecognizer(panFontGestureRecognizer)
}

⭐️ 加入貼圖

  • Scroll view 裝貼圖按鈕
  • 點貼圖在 image view 中間加入顯示貼圖的 imageView
  • UIPanGestureRecognizer 拖曳手勢移動貼圖位置
  • UIPinchGestureRecognizer 捏合手勢縮放貼圖大小

1. 宣告儲存貼圖的變數

var sticker = UIImageView()

2. 加入貼圖的 IBAction function

@IBAction func AddSticker(_ sender: UIButton){
//設定貼圖的外框大小
sticker.frame.size = CGSize(width: 100, height: 100)
//圖片的顯示模式為AspectFit,才能完整顯示而且不改變圖片原本比例
sticker.contentMode = .scaleAspectFit
//讓圖片出現在imageView中間
sticker.center = imageView.center
//顯示的貼圖為點選的按鈕所對應的圖片名稱(參考下面附的兩張圖)
sticker.image = UIImage(named: "sticker-" + "\(sender.tag)")
//把貼圖加到containerView中
containerView.addSubview(sticker)
}

3. 設定 UIPanGestureRecognizer 拖曳手勢 objc function,並在 viewDidLoad 中設定 UIPanGestureRecognizer 的相關屬性

//拖曳貼圖的function
@objc func moveSticker(_ sender: UIPanGestureRecognizer){
let point = sender.location(in: self.containerView)
sticker.center = point
}
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = selectItem

//設定拖曳手勢,用在移動貼圖
let panStickerGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(moveSticker(_:)))

//設定單點觸控拖曳
panStickerGestureRecognizer.minimumNumberOfTouches = 1
panStickerGestureRecognizer.maximumNumberOfTouches = 1
sticker.isUserInteractionEnabled = true
sticker.addGestureRecognizer(panStickerGestureRecognizer)

4. 設定 UIPinchGestureRecognizer 捏合手勢 objc function,並在 viewDidLoad 中設定 UIPinchGestureRecognizer 的相關屬性

//縮放貼圖的function
@objc func pinchSticker(_ sender: UIPinchGestureRecognizer){
//當兩根手指的捏合狀態改變時
if sender.state == .changed{
//宣告property儲存縮放範圍
let scale = sender.scale
//宣告property儲存原本貼圖的外框bounds的寬度
let w = sticker.bounds.size.width

//讓縮放範圍為貼圖的寬為原本的寬的0.3~2倍
if w * scale > w * 0.3 && w * scale <= w * 2{
//等比例縮放貼圖(長跟寬都乘以scale倍)
sticker.transform = CGAffineTransform(scaleX: scale, y: scale)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = selectItem
//設定捏合手勢,用在縮放貼圖
let pinchStickerGestureRecognzier = UIPinchGestureRecognizer(target: self, action: #selector(pinchSticker(_:)))
//將捏合手勢加入貼圖中
sticker.addGestureRecognizer(pinchStickerGestureRecognzier)
}

✨ 這邊要補充說明 iOS SDK 的 frame 跟 bounds,我覺得這個在這次作業裡面好重要!

每個元件都會有他的 frame 跟 bounds,兩個都是指元件的外框,能取得 x、y、width、height 的值,但是 frame 是相對於 superview 的位置跟大小,也就是我們直接能看到改變的部分,移動時 x、y 改變,放大縮小時 width、height 改變;bounds 則是這個元件在自己的座標系統中的位置跟大小,通常 不管怎麼移動 x、y 都不會改變,width、height 會跟著 frame 改變而改變,但是如果是利用「transform」來移動、旋轉或是縮放元件的話,bounds 是會很固執的維持原樣的😂

詳細內容可以參考彼得潘的文章:

最後一篇也會使用到 frame 跟 bounds,真的很重要!!!

💡 續集們

參考資料

使用的素材

Camera vector created by freepik — www.freepik.com

Planner vector created by freepik — www.freepik.com

Note design vector created by rawpixel.com — www.freepik.com

--

--