#10.1照片編輯

功能:

  • 水平,垂直,90度左右翻轉照片.
  • 改變圖片比例.
  • 改變圖片背景顏色.
  • 裁切圖片.

程式重點:

  • UIImagePickerController使用
  • UICollectionViewFlowLayout設定
  • FileManager使用
  • 程式生成UIView(Init)
  • View的touchesBegan,touchesMoved,touchesEnded函式使用
  • instantiateViewController(傳值,建構子)
  • CGAffineTransform動畫使用
  • UIColorPickerViewController使用
  • UIGraphicsImageRenderer存圖

分析和解構流程:

1.生成Photo資料型別,定義儲存位置

2.撰寫TableView的Cell生成

3.撰寫UIImagePickerController(相簿選圖),包含使用instantiateViewController進行換頁和傳值

4,撰寫用程式生成View,包含使用touchesBegan等函式

5.撰寫編輯圖片功能的邏輯

6.撰寫呈現TableView畫面的程式邏輯

感想:上網找了很多範例,理解和嘗試後,拼拼湊湊完成了這個程式.有很多有趣的東西,有些寫法以前沒有寫過.大致上來說是一個很有趣的經驗.分兩個部分上傳,這樣比較好理解.

讓我們開始Coding吧!

生成Photo資料型別

struct Photo:Codable{
var imageName:String

var photoUrl: URL {
//檔名
Photo.documentsDirectory.appendingPathComponent(imageName)
}

static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

static let archiveURL = documentsDirectory.appendingPathComponent("photo")
.appendingPathExtension("plist")

static func readPhoto() -> [Photo]?{
let propertyDecoder = PropertyListDecoder()
guard let photoList = try? Data(contentsOf: archiveURL) else{ return nil }
return try? propertyDecoder.decode([Photo].self, from: photoList)

}
static func savePhotoList(photoList:[Photo]){
let propertyListEncoder = PropertyListEncoder()
let photoList = try? propertyListEncoder.encode(photoList)
try? photoList?.write(to:archiveURL,options: .noFileProtection)
}
}

BarButtonItem生成

//選圖
let imagePickerController = UIImagePickerController()

var photos = [Photo]()

override func viewDidLoad() {
super.viewDidLoad()
//生成navigationItem.rightBarButtonItem
navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addNewPerson))
navigationItem.rightBarButtonItem?.tintColor = .systemPurple
navigationItem.title = "Edit Picture Now!"
}

//要彈出alert視窗
//過去下一頁
//把圖片傳過去
@objc func addNewPerson() {
//生成alert

let controller = UIAlertController(title: "Edit Team Photo", message: "Choose source", preferredStyle: .actionSheet)
//相機
let takePhotoAction = UIAlertAction(title: "Camera", style: .default) { _ in
self.imagePickerController.sourceType = .camera
self.imagePickerController.delegate = self
self.present(self.imagePickerController, animated: true)
}
//Library
let chooseFromLibraryAction = UIAlertAction(title: "Choose From Library", style: .default) { _ in
self.imagePickerController.sourceType = .photoLibrary
self.imagePickerController.delegate = self
self.present(self.imagePickerController, animated: true)
}
//取消
let cancelAction = UIAlertAction(title: "Cancel", style: .destructive) { _ in
self.dismiss(animated: true)
}
controller.addAction(takePhotoAction)
controller.addAction(chooseFromLibraryAction)
controller.addAction(cancelAction)

present(controller, animated: true)
}

//相簿選圖
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

//生成圖片
let image = info[.originalImage] as? UIImage

//生成RetouchingViewController,傳值.下一頁用變數接起來
if let vc = storyboard?.instantiateViewController(identifier: "RetouchingViewController", creator: { coder in
RetouchingViewController(coder: coder, editImage: image!,photos: self.photos)}){
show(vc, sender: nil)
}
print("didFinishPickingMediaWithInfo")
dismiss(animated: true,completion: nil)
}

TableView的Cell生成

override func numberOfSections(in collectionView: UICollectionView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}


override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of items
return photos.count
}

override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Photo", for: indexPath) as! PhotoCollectionViewCell

let photo = photos[indexPath.item]


cell.imageView.image = UIImage(contentsOfFile: photo.photoUrl.path())


// Configure the cell

return cell
}

//點選Item
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let photo = photos[indexPath.item]

let title = "Do you want to delete?"
let message = ""
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Cancel", style: .default))

alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { alert in
//移除陣列
self.photos.remove(at: indexPath.item)
//砍掉預設值
try? FileManager.default.removeItem(at: photo.photoUrl)
//存取
Photo.savePhotoList(photoList: self.photos)
collectionView.reloadData()
self.setIllustrationView()
}))
present(alert, animated: true, completion: nil)
}

程式生成View,包含使用touchesBegan等函式

class EditImageUIView: UIView {

enum Edge {
case topLeft, topRight, bottomLeft, bottomRight, none
}

//預設編輯Size 一個手指點擊的大小
static var edgeSize: CGFloat = 44.0

let screenW = UIScreen.main.bounds.width // 作為初始view長寬
let screenH = UIScreen.main.bounds.height
let imageInitW: CGFloat
let imageInitH: CGFloat

//當前範圍 是一個四方形
var currentEdge: Edge = .none

//選的點
var touchStart = CGPoint.zero

//view 包UIImageView 包UIImage
var imageView = UIImageView()
let originalImage: UIImage

//生成View元件
init?(frame: CGRect, editImage: UIImage) {
self.originalImage = editImage
self.imageInitW = editImage.size.width
self.imageInitH = editImage.size.height
super.init(frame: frame)

imageView.image = editImage
self.clipsToBounds = true
self.backgroundColor = .systemGray6

// 定位
editInitialize()
self.addSubview(imageView)
print("EditImageUIView",imageView)

}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

// 設定 view的位置
func editInitialize(){
//等比 置中
self.frame = CGRect(x: 0, y: (screenH-screenW)/2, width: screenW, height: screenW)

//圖片 尺寸比例
imageView.frame.size = CGSize(width: screenW, height: (screenW*imageInitH)/imageInitW)
//圖片x,y位置
imageView.frame.origin = CGPoint(x: (screenW-imageView.frame.width)/2, y: (screenW-imageView.frame.height)/2)
// 使用原圖片
imageView.image = originalImage
}

使用touchesBegan等函式

//開始拖動,先判別拖選的點在哪
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// 剪裁
guard currentMode == .typeCrop else {
return
}

//尋找集合當中的點
if let touch = touches.first {
//location 觸控當前位置 範圍是view
touchStart = touch.location(in: self)

//創造當前頁面範圍
currentEdge = {
//如果view的bounds的size的寬 減去touchStart的x
if self.bounds.size.width - touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomRight
//右下角
} else if touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topLeft
//左上角
} else if self.bounds.size.width-touchStart.x < Self.edgeSize && touchStart.y < Self.edgeSize {
return .topRight
//右上角
} else if touchStart.x < Self.edgeSize && self.bounds.size.height - touchStart.y < Self.edgeSize {
return .bottomLeft
//左下角
}
return .none
}()
}
}

//拖動範圍,判別哪個點被拖動,同時重新設定origin
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard currentMode == .typeCrop else {
return
}

if let touch = touches.first {
//返回給定檢視的座標系統中觸控的當前位置
let currentPoint = touch.location(in: self)
//返回給定檢視的座標系統中觸控的先前位置
let previous = touch.previousLocation(in: self)

//view 左上角x位置
let originX = self.frame.origin.x
//view 左上角y位置
let originY = self.frame.origin.y
//view 的寬
let width = self.frame.size.width
//view 的高
let height = self.frame.size.height
//尋找 前點 跟後點 之後x座標差
let deltaWidth = currentPoint.x - previous.x
//尋找 前點 跟後點 之後y座標差
let deltaHeight = currentPoint.y - previous.y

switch currentEdge {
//左上
case .topLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY + deltaHeight, width: width - deltaWidth, height: height - deltaHeight)
//重設置image左上角的原點
imageView.frame.origin.x -= deltaWidth
imageView.frame.origin.y -= deltaHeight
case .topRight:
//frame的x,y是原點
//拖移位置移動後的frame originX originY是指原點
self.frame = CGRect(x: originX, y: originY + deltaHeight, width: width + deltaWidth, height: height - deltaHeight)
imageView.frame.origin.y -= deltaHeight
case .bottomRight:
self.frame = CGRect(x: originX, y: originY, width: width + deltaWidth, height: height + deltaWidth)
case .bottomLeft:
self.frame = CGRect(x: originX + deltaWidth, y: originY, width: width - deltaWidth, height: height + deltaHeight)
default:
break
}
}
}

//編輯完成,設定圖片位置和寬高
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard currentMode == .typeCrop else {
return
}
currentEdge = .none
//如果view.frame的寬 大於 螢幕的寬的話
if (self.frame.width > UIScreen.main.bounds.width) {
self.frame.size.width = UIScreen.main.bounds.width
}
//如果view.frame的高 大於 螢幕的寬的話
if (self.frame.height > UIScreen.main.bounds.width) {
self.frame.size.height = UIScreen.main.bounds.width
}
//調整完之後強制 置中
self.frame.origin.x = (screenW-self.frame.width)/2
self.frame.origin.y = (screenH-self.frame.height)/2
}

程式參考:

下載檔案後,13-Project10,為Apple範例檔.

--

--