#10.1照片編輯
Published in
18 min readDec 6, 2022
功能:
- 水平,垂直,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範例檔.