diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorModel.swift b/SignalMessaging/Views/ImageEditor/ImageEditorModel.swift index 1407eef684..e01f41a0c6 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorModel.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorModel.swift @@ -139,6 +139,10 @@ public class OrderedDictionary: NSObject { return OrderedDictionary(keyValueMap: keyValueMap, orderedKeys: orderedKeys) } + public func value(forKey key: KeyType) -> ValueType? { + return keyValueMap[key] + } + public func append(key: KeyType, value: ValueType) { if keyValueMap[key] != nil { owsFailDebug("Unexpected duplicate key in key map: \(key)") @@ -239,6 +243,11 @@ public class ImageEditorContents: NSObject { return ImageEditorContents(itemMap: itemMap.clone()) } + @objc + public func item(forId itemId: String) -> ImageEditorItem? { + return itemMap.value(forKey: itemId) + } + @objc public func append(item: ImageEditorItem) { Logger.verbose("\(item.itemId)") @@ -300,6 +309,7 @@ private class ImageEditorOperation: NSObject { @objc public protocol ImageEditorModelDelegate: class { func imageEditorModelDidChange() + func imageEditorModelDidChange(changedItemIds: [String]) } // MARK: - @@ -360,6 +370,11 @@ public class ImageEditorModel: NSObject { return contents.items() } + @objc + public func item(forId itemId: String) -> ImageEditorItem? { + return contents.item(forId: itemId) + } + @objc public func canUndo() -> Bool { return !undoStack.isEmpty @@ -382,6 +397,7 @@ public class ImageEditorModel: NSObject { self.contents = undoOperation.contents + // We could diff here and yield a more narrow change event. delegate?.imageEditorModelDidChange() } @@ -397,6 +413,7 @@ public class ImageEditorModel: NSObject { self.contents = redoOperation.contents + // We could diff here and yield a more narrow change event. delegate?.imageEditorModelDidChange() } @@ -404,7 +421,7 @@ public class ImageEditorModel: NSObject { public func append(item: ImageEditorItem) { performAction({ (newContents) in newContents.append(item: item) - }) + }, changedItemIds: [item.itemId]) } @objc @@ -420,10 +437,11 @@ public class ImageEditorModel: NSObject { public func remove(item: ImageEditorItem) { performAction({ (newContents) in newContents.remove(item: item) - }) + }, changedItemIds: [item.itemId]) } private func performAction(_ action: (ImageEditorContents) -> Void, + changedItemIds: [String], suppressUndo: Bool = false) { if !suppressUndo { let undoOperation = ImageEditorOperation(contents: contents) @@ -435,6 +453,6 @@ public class ImageEditorModel: NSObject { action(newContents) contents = newContents - delegate?.imageEditorModelDidChange() + delegate?.imageEditorModelDidChange(changedItemIds: changedItemIds) } } diff --git a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift index 1465bc34bc..c14a638ee3 100644 --- a/SignalMessaging/Views/ImageEditor/ImageEditorView.swift +++ b/SignalMessaging/Views/ImageEditor/ImageEditorView.swift @@ -101,8 +101,6 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate { public func handleTouchGesture(_ gestureRecognizer: UIGestureRecognizer) { AssertIsOnMainThread() - Logger.verbose("\(NSStringForUIGestureRecognizerState(gestureRecognizer.state))") - let removeCurrentStroke = { if let stroke = self.currentStroke { self.model.remove(item: stroke) @@ -163,13 +161,17 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate { // MARK: - ImageEditorModelDelegate public func imageEditorModelDidChange() { - // TODO: We eventually want to narrow our change events - // to reflect the specific item(s) which changed. updateAllContent() updateButtons() } + public func imageEditorModelDidChange(changedItemIds: [String]) { + updateContent(changedItemIds: changedItemIds) + + updateButtons() + } + // MARK: - Accessor Overrides @objc public override var bounds: CGRect { @@ -190,33 +192,71 @@ public class ImageEditorView: UIView, ImageEditorModelDelegate { // MARK: - Content - var contentLayers = [CALayer]() + var contentLayerMap = [String: CALayer]() internal func updateAllContent() { AssertIsOnMainThread() - for layer in contentLayers { - layer.removeFromSuperlayer() - } - contentLayers.removeAll() - - guard bounds.width > 0, - bounds.height > 0 else { - return - } - // Don't animate changes. CATransaction.begin() CATransaction.setDisableActions(true) - for item in model.items() { - guard let layer = ImageEditorView.layerForItem(item: item, - viewSize: bounds.size) else { - continue - } + for layer in contentLayerMap.values { + layer.removeFromSuperlayer() + } + contentLayerMap.removeAll() - self.layer.addSublayer(layer) - contentLayers.append(layer) + if bounds.width > 0, + bounds.height > 0 { + + for item in model.items() { + guard let layer = ImageEditorView.layerForItem(item: item, + viewSize: bounds.size) else { + continue + } + + self.layer.addSublayer(layer) + contentLayerMap[item.itemId] = layer + } + } + + CATransaction.commit() + } + + internal func updateContent(changedItemIds: [String]) { + AssertIsOnMainThread() + + // Don't animate changes. + CATransaction.begin() + CATransaction.setDisableActions(true) + + // Remove all changed items. + for itemId in changedItemIds { + if let layer = contentLayerMap[itemId] { + layer.removeFromSuperlayer() + } + contentLayerMap.removeValue(forKey: itemId) + } + + if bounds.width > 0, + bounds.height > 0 { + + // Create layers for inserted and updated items. + for itemId in changedItemIds { + guard let item = model.item(forId: itemId) else { + // Item was deleted. + continue + } + + // Item was inserted or updated. + guard let layer = ImageEditorView.layerForItem(item: item, + viewSize: bounds.size) else { + continue + } + + self.layer.addSublayer(layer) + contentLayerMap[item.itemId] = layer + } } CATransaction.commit()