Я создаю приложение, в котором пользователь может добавить изображение в «холст», а также изменить его размер и переместить изображение, используя средства распознавания жестов и панорамирования. Представление изображения является пользовательским, которое я создал, используя эту статью: Пути Безье и Распознаватели жестов
Это работает очень хорошо. Изображение изменяется и перемещается очень плавно. Я могу захватить центр и размер изображения после жестов панорамирования и сжатия. Проблема в том, что после сохранения размера и координат изображения оно не соответствует тем, которые я загружаю обратно в «холст». Это как если бы центр был смещен на число пикселей "X".
Вот мой код для создания изменяемого размера и подвижного изображения:
'' '
import UIKit
import Foundation
class MovableImage: UIImageView {
let size: CGFloat = 150.0
var imageMovedHandler:((_ x: CGFloat, _ y: CGFloat) -> ())?
var imageDeletedHandler:((_ delete: Bool) -> ())?
var longPressHandler:((_ selected: Bool) -> ())?
var imageSizeChangedHandler:((_ newImageView: MovableImage) -> ())?
let deleteButton = UIButton(type: .close)
init(origin: CGPoint) {
super.init(frame: CGRect(x: origin.x, y: origin.y, width: size, height: size)) //
debugCenterDot()
initGestureRecognizers()
}
//added a dot to try and understand what is happening with the "center" of the imageview, but it didn't show in the center of the imageview
func debugCenterDot() {
let dot = UIBezierPath(ovalIn: CGRect(x: self.center.x, y: self.center.y, width: 15, height: 15))
let dotLayer = CAShapeLayer()
dotLayer.path = dot.cgPath
dotLayer.strokeColor = UIColor.yellow.cgColor
self.layer.addSublayer(dotLayer)
self.setNeedsDisplay()
}
internal func addButton() {
deleteButton.tintColor = UIColor.red
deleteButton.backgroundColor = UIColor.white
deleteButton.addTarget(self, action: #selector(deleteSelf(sender:)), for: .touchUpInside)
deleteButton.frame = .zero //CGRect(x: 8, y: 8, width: 15, height: 15)
deleteButton.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(deleteButton)
NSLayoutConstraint.activate([
deleteButton.widthAnchor.constraint(equalToConstant: 15),
deleteButton.widthAnchor.constraint(equalTo: deleteButton.heightAnchor),
deleteButton.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 8),
deleteButton.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 8),
])
}
@objc func deleteSelf(sender: UIButton) {
imageDeletedHandler?(true)
self.removeFromSuperview()
}
func initGestureRecognizers() {
let panGR = UIPanGestureRecognizer(target: self, action: #selector(didPan(panGR:)))
addGestureRecognizer(panGR)
let pinchGR = UIPinchGestureRecognizer(target: self, action: #selector(didPinch(pinchGR:)))
addGestureRecognizer(pinchGR)
let longPressGR = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(longPressGR:)))
longPressGR.minimumPressDuration = 1
addGestureRecognizer(longPressGR)
}
@objc func didLongPress(longPressGR: UILongPressGestureRecognizer) {
self.superview!.bringSubviewToFront(self)
self.layer.borderWidth = 2
self.layer.borderColor = UIColor.red.cgColor
addButton()
longPressHandler?(true)
}
@objc func didPan(panGR: UIPanGestureRecognizer) {
self.superview!.bringSubviewToFront(self)
if self.layer.borderWidth == 2 {
let translation = panGR.translation(in: self)
print("BEFORE PAN: \(self.center)")
self.center.x += translation.x
self.center.y += translation.y
print("AFTER PAN: \(self.center)")
panGR.setTranslation(CGPoint.zero, in: self)
if panGR.state == .ended {
imageMovedHandler?(self.center.x, self.center.y)
self.layer.borderWidth = 0
self.layer.borderColor = nil
self.deleteButton.removeFromSuperview()
}
}
}
@objc func didPinch(pinchGR: UIPinchGestureRecognizer) {
self.superview?.bringSubviewToFront(self)
if self.layer.borderWidth == 2 {
let scale = pinchGR.scale
self.transform = CGAffineTransform(scaleX: scale, y: scale)
if pinchGR.state == .ended {
imageSizeChangedHandler?(self)
}
}
}
func scaleOf(transform: CGAffineTransform) -> CGPoint {
let xscale = sqrt(transform.a * transform.a + transform.c * transform.c)
let yscale = sqrt(transform.b * transform.b + transform.d * transform.d)
return CGPoint(x: xscale, y: yscale)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
'' '
И вот как я загружаю его обратно в «холст» и сохраняю его обратно в CoreData (сохраняйте функции в замыканиях в нижней части функции загрузки):
'' '
func loadImages(new: Bool, enableInteraction: Bool) {
let pagePhotos = LoadPagePhotos()
var pageImages: [NSManagedObject] = []
var x: CGFloat?
var y: CGFloat?
var width: CGFloat?
var height: CGFloat?
var image: UIImage?
if isYear {
pageImages = pagePhotos.resetYearPhotosPositions(journalName: journalName, yearPosition: yearPosition)
} else {
pageImages = pagePhotos.resetPhotosPositions(journalName: journalName, monthName: monthName, weekPosition: positionWeek)
}
scrollView.mainView.newImages.forEach { i in
i.removeFromSuperview()
}
scrollView.mainView.newImages.removeAll()
if pageImages.count > 0 {
pageImages.forEach{ (photo) in
x = CGFloat((photo.value(forKey: "pageImageX") as? Float)!)
y = CGFloat((photo.value(forKey: "pageImageY") as? Float)!)
height = CGFloat((photo.value(forKey: "pageImageSizeHeight") as? Float)!)
width = CGFloat((photo.value(forKey: "pageImageSizeWidth") as? Float)!)
image = photo.value(forKey: "image") as? UIImage
let thisImage: MovableImage = MovableImage(origin: CGPoint.zero)
thisImage.contentMode = .scaleAspectFit
thisImage.center = CGPoint(x: x!, y: y!)
thisImage.image = image!
thisImage.frame.size.height = height!
thisImage.frame.size.width = width!
scrollView.mainView.addSubview(thisImage)
scrollView.mainView.newImages.append(thisImage)
if enableInteraction {
thisImage.isUserInteractionEnabled = true
} else {
thisImage.isUserInteractionEnabled = false
}
thisImage.layer.zPosition = 1
thisImage.layer.borderWidth = 0
if new {
imageOptionsMenuView.isHidden = false
} else {
imageOptionsMenuView.isHidden = true
}
movableImage = thisImage
//for clarity sake I moved the save functions to separate block here in stack overflow so it is easier to read
}
}
' ''
Замыкания "imageMovedHandler" и "imageSizeChangedHandler" используются для обнаружения, когда выполняется перемещение и изменение размера, и я сохраняю изображение в CoreData. Вот они:
'' '
if movableImage != nil {
movableImage?.imageMovedHandler = { [unowned self] (x, y) in
if self.isYear {
if self.scrollView.mainView.newImages.count > 0 {
for (idx, i) in self.scrollView.mainView.newImages.enumerated() {
if i.layer.borderWidth == 2 {
let id = idx + 1
_ = LoadPagePhotos().updateYearPhoto(journalName: self.journalName, yearPosition: self.yearPosition, pageImageId: id, imageHeight: Float(i.frame.size.height), imageWidth: Float(i.frame.size.width), imageX: Float(i.center.x), imageY: Float(i.center.y), pagePhoto: i.image!, photoPath: nil)
}
}
}
} else {
if self.scrollView.mainView.newImages.count > 0 {
for (idx, i) in self.scrollView.mainView.newImages.enumerated() {
if i.layer.borderWidth == 2 {
let id = idx + 1
_ = LoadPagePhotos().updatePhoto(journalName: self.journalName, monthName: self.monthName, weekPosition: self.positionWeek, pageImageId: id, imageHeight: Float(i.frame.size.height), imageWidth: Float(i.frame.size.width), imageX: Float(i.center.x), imageY: Float(i.center.y), pagePhoto: i.image!, photoPath: nil)
}
}
}
}
self.loadImages(new: false, enableInteraction: true)
}
movableImage?.imageSizeChangedHandler = { [unowned self] (newImageView) in
var id = 0
var img = 0
if self.isYear {
if self.scrollView.mainView.newImages.count > 0 {
for (idx, i) in self.scrollView.mainView.newImages.enumerated() {
if i.layer.borderWidth == 2 {
id = idx + 1
img = idx
}
}
self.scrollView.mainView.newImages[img] = newImageView
_ = LoadPagePhotos().updateYearPhoto(journalName: self.journalName, yearPosition: self.yearPosition, pageImageId: id, imageHeight: Float(newImageView.frame.size.height), imageWidth: Float(newImageView.frame.size.width), imageX: Float(newImageView.center.x), imageY: Float(newImageView.center.y), pagePhoto: newImageView.image!, photoPath: nil)
}
} else {
if self.scrollView.mainView.newImages.count > 0 {
for (idx, i) in self.scrollView.mainView.newImages.enumerated() {
if i.layer.borderWidth == 2 {
id = idx + 1
img = idx
}
}
self.scrollView.mainView.newImages[img] = newImageView
_ = LoadPagePhotos().updatePhoto(journalName: self.journalName, monthName: self.monthName, weekPosition: self.positionWeek, pageImageId: id, imageHeight: Float(newImageView.frame.size.height), imageWidth: Float(newImageView.frame.size.width), imageX: Float(newImageView.center.x), imageY: Float(newImageView.center.y), pagePhoto: newImageView.image!, photoPath: nil)
}
}
self.loadImages(new: false, enableInteraction: true)
}
}
}
' ''
Вот изображение того, что происходит, когда я перемещаю изображение по холсту: Это первоеНа рисунке показано, где я перестал перемещать изображение в:
Это изображение показывает, куда было загружено изображение после сохранения размера и координат:
Желаемый результат:
- При сжатии, панорамировании и сохранении изображения, а затем загрузке изображение сохраняет свои текущие координаты и размер на холсте.
РЕДАКТИРОВАТЬ: Следует также отметить, что смещение изображения при его перемещении происходит только после "масштабирования