Implementing redo alongside undo with UndoManager in drawing application Swift 3 - ios

I am working on a project that includes an annotation tool allowing users to "draw" on documents with finger gestures or a pencil. Naturally, I'm keen on implementing undo/redo for drawn paths.
My implementation for the drawing app is relatively conventional. What the user sees on the screen is the combination of a cached bitmap image (a snapshot of all the paths that have been drawn before the current one) together with a "live" rendering of the current path (a UIBezierPath). When touchesEnded is triggered, the new path is added to the bitmap.
I have been able to implement undo with relatively little trouble. I have created a standard undoManager for the class:
let myUndoManager : UndoManager = {
let mUM : UndoManager = UndoManager()
mUM.levelsOfUndo = 6
return mUM
}()
The function that is called at the end of touchesEnded to render the new cached path is called drawBitmap. At the start of this function, assuming there is a previous cached path and before drawing the new one, I register the following undo action with the undo manager:
let previousCachedPath : UIImage = self.cachedPath
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: previousCachedPath)
setBitmap(_ previousCachedPath : UIImage) is a function that resets the displayed bitmap to the provided image.
I have a undo/redo buttons linked to undo() and redo() methods respectively. Aside from some logic dictating when these buttons should be active (i.e. to make sure you can't press undo when nothing has been drawn etc.), these simply call myUndoManager.undo() and myUndoManager.redo() respectively:
func undo() -> Void {
guard self.myUndoManager.canUndo else { return }
self.myUndoManager.undo()
if !self.redoButton.isEnabled {
self.redoButton.isEnabled = true
}
if !self.myUndoManager.canUndo {
self.undoButton.isEnabled = false
}
self.setNeedsDisplay()
}
func redo() -> Void {
guard self.myUndoManager.canRedo else { return }
self.myUndoManager.redo()
if !self.undoButton.isEnabled {
self.undoButton.isEnabled = true
}
if !self.myUndoManager.canRedo {
self.redoButton.isEnabled = false
}
self.setNeedsDisplay()
}
As I mentioned, undo works perfectly to the specified six levels of undoability. However, I'm clearly missing something with redo. My initial hope was that the undoManager would automatically transfer undo tasks from the undo stack to the redo stack when undo is called, but this is clearly not happening.
I have already searched for answers, and I think the closest to what I need may to use mutual recursion as per:
Using NSUndoManager, how to register undos using Swift closures
However, I have been unable to make this work. Any help therefore appreciated!

Thanks to #matt 's help, I solved this by putting everything in the setBitmap(_:) function. To try and better understand things, I implemented both the registerUndo(withTarget:selector:) approach:
func setBitmap(_ toCachedPath : UIImage) -> Void {
self.myUndoManager.registerUndo(withTarget: self, selector: #selector(self.setBitmap(_:)), object: self.cachedPath)
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
And also with the prepare(withInvocationTarget:) approach:
func setBitmap(_ toCachedPath : UIImage) -> Void {
if self.cachedPath != nil {
(self.rWUndoManager.prepare(withInvocationTarget: self) as AnyObject).setBitmap(self.cachedPath)
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
Hope that helps anybody else who were scratching their heads as much as I was.

Actually you could use the closure version registerUndo(withTarget:handler:) for redo to work too. Just make sure it would work with the selector one registerUndo(withTarget:selector:object:) first, i.e. make a function which takes only one parameter, like in your answer. And then you can replace the selector-based method with the closure one:
func setBitmap(_ toCachedPath : UIImage) -> Void {
let oldCachedPath = cachedPath // For not referencing it with `self` in the closure.
myUndoManager.registerUndo(withTarget: self) {
$0.setBitmap(oldCachedPath)
}
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
toCachedPath.draw(at: CGPoint.zero)
self.cachedPath = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
}
I guess the closure version is just a repackage of the selector version which recognises only one method and one input parameter, so we still have to write like this for it to work.

Related

If variable is not set, wait for variable to be set

I have 2 delegate methods that are being called by notifications from a 3rd party library.
Method 1:
mediaContentWasUpdated()
Method 2:
adMediaDidBeginPlaying()
In Method 1, a key variable (adDuration) is set from a parameter that is passed in with the notification. As far as I can see this is the only place to get this information.
In Method 2, we check the adDuration and if it is greater than 0 then we update the UI to reflect that we are in fact play an ad.
A bug has appeared where sometimes these two methods are called in the wrong order. Meaning the adDuration is not set and Method 2 thinks there is no ad media to be played and does not update the UI accordingly.
My current attempt at a solution is to make adDuration optional and use an NSCondition to cause Method 2 to wait for Method 1 to set adDuration and then proceed.
var adDuration : Double?
let condition = NSCondition()
func mediaContentWasUpdated(notification: NSNotificiation) {
condition.lock()
if(notificationHasAdDurationInfo(notification)) {
self.adDuration = getAdDuration(notification)
condition.signal()
}
condition.unlock()
}
func adMediaDidBeginPlaying(notification: NSNotification) {
condition.lock()
while adDuration == nil {
condition.wait()
}
if adDuration! > Double(0) {
updateUIForAd()
}
condition.unlock()
}
This is my first time trying something like this and I worry I am doing something wrong. I also have some concerns about locking and unlocking threads needlessly (which would happen in a well timed run, or if there were no ad content to be played).
Outside factors are hindering my ability to test and I wanted to get some input to see if I am heading in the right direction while I wait for those issues to be resolved.
Your discussion of NSCondition got me on the same track with you, and I built two or three solutions using DispatchGroup (which is the better tool for this), but they always had little corner cases that could behave badly, and didn't really capture the intent.
(If you're interested in the DispatchGroup solutions, they're of the form: call .enter() in init, call .leave() when the duration comes in, call notify() when the playing starts. It works fine, but it introduces corner cases that can crash, just like NSCondition.)
Getting back to the real intent:
Update the UI when the duration is known and the ad has started playing.
There's no concurrency going on here. So pulling out GCD is not just overkill; it actually makes things worse because it introduces lots of complicated corner cases.
So I thought about how I'd have solved this back before GCD. And the answer is obvious: just check if you have the data you want, and then do the thing. (Reading through the comments, I see Paulw11 pointed this out as well.)
Personally I like to pull this kind of thing into its own type to make things more self-contained. I hate some of the names here, but the idea should be clear:
class AdPlayer {
private var readyToPlay = false
private var duration: Double = 0.0
private let completion: (Double) -> Void
func setDuration(from notification: Notification) {
if(notificationHasAdDurationInfo(notification)) {
duration = getAdDuration(notification)
}
playIfReady()
}
func play() {
readyToPlay = true
playIfReady()
}
private func playIfReady() {
if duration > 0 && readyToPlay {
completion(duration)
}
}
init(completion: #escaping (Double) -> Void) {
self.completion = completion
}
}
When you set each thing, see if you're ready to update, and if so, update. I've gotten rid of the optional as well, since I believe the intent is "0 duration is always wrong." But you could use an Optional so you could detect actually receiving a 0 from the notification.
With that, you just set up a player property:
player = AdPlayer(completion: updateUIForAd)
(Note that the above might be creating a retain loop, depending on what updateUIForAd is; you may need a [weak self] closure or the like here.)
And then update it as needed:
func mediaContentWasUpdated(notification: NSNotificiation) {
player.setDuration(from: notification)
}
func adMediaDidBeginPlaying(notification: NSNotification) {
player.play()
}
A big advantage of creating the AdPlayer type is that it's easy to reset the system when the ad is done (or if something goes wrong). Just throw away the whole object and create another one.

Extend SKAction to override timingMode

I have many SKActions in a SpriteKit project. The default timingMode for SKActions is "linear". Is it possible to use an extension to override this timingMode default to e.g. "easeInEaseOut" so ALL SKActions have timingMode = easeInEaseOut?
I have tried various "extension" styles but none will compile - normally returning "'timingMode' used within its own type" or "Initializer 'init()' with Objective-C selector 'init' conflicts with implicit initializer 'init()' with the same Objective-C selector"
The docs don't seem to give any examples of this, but surely this would be a useful thing to be able to do? Especially when you have hundreds of SKActions in your game?
Pick your poison, one extends the action to allow you to quickly call .easeInEaseOut timing mode, the other extends SKNode to allow you to run using a specific timing mode.
There is no way to change default behavior, the only other way is to create your own static methods for every action that exists, which can become cumbersome.
extension SKAction
{
//e.g. SKAction.move(to: CGPoint.zero, duration: 10).easeInEaseOut()
func easeInEaseOut() -> SKAction
{
self.timingMode = .easeInEaseOut
return self
}
}
extension SKNode
{
func runWithEaseInEaseOut(action:SKAction,withKey key: String = "")
{
action.timingMode = .easeInEaseOut
if key != ""
{
self.run(action,withKey:key)
}
else
{
self.run(action)
}
}
}

Customizing QuickType Suggestions in iOS

Well, I suspect that this is a forlorn question, ultimately doomed to sadden and disappoint me, but I want to have the question on the record for the future.
I will add this to the existing questions: here and here
The scenario is that I am creating an administration app that is designed to allow folks to edit the values in a database on a server. I have a fairly zippy API that can be used to allow REST-style data exchange.
I have the ability to download a list of values that the user can apply as search terms when they log in, and I'd like to be able to help them to enter things quickly.
For example, town names. If they enter "B", then I'd like to be able to offer "Bayshore", "Babylon" and "Bohemia" as suggestions, and so on.
A completely legit application.
I am under the assumption that there currently does not exist a QuickType API.
Am I wrong?
There is no QuickType API but you can get similar functionality with the ACEAutocompleteBar library from GitHub. Just for the record, I have listed the steps to get this working with Swift:
1) Import the files from the folder "ACEAutocompleteBar" to your project.
2) Create a bridging header and write #import "ACEAutocompleteBar.h" at the top.
3) Make your view containing the text field an ACEAutocompleteDataSource and ACEAutocompleteDelegate.
4) Implement the minimumCharactersToTrigger function
func minimumCharactersToTrigger(inputView: ACEAutocompleteInputView!) -> UInt {
return 1
}
5) Implement the inputView function.
func inputView(inputView: ACEAutocompleteInputView!, itemsFor query: String!, result resultBlock: (([AnyObject]!) -> Void)!) {
inputView.hidden = false
inputView.alpha = 0.75
if resultBlock != nil{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
var data:NSMutableArray = []
if(self.myTextField.isFirstResponder()){
//query your data source with 'query' and add any of the results to data to pass back
}
dispatch_async(dispatch_get_main_queue()) {resultBlock(data as [AnyObject])}
}
}
}
6) Implement the textField function.
func textField(textField: UITextField!, didSelectObject object: AnyObject!, inInputView inputView: ACEAutocompleteInputView!) {
textField.text = String(object)
}
7) Call setAutocompleteWithDataSource on the textField.
self.myTextField.setAutocompleteWithDataSource(self, delegate: self, customize: {
inputView in
// customize the view (optional)
inputView.font = UIFont.systemFontOfSize(20)
inputView.textColor = UIColor.whiteColor()
inputView.backgroundColor = UIColor.blueColor()
inputView.hidden = false
})
Hope that helps anyone looking for this kind of functionality!

UITapGestureRecognizer Causing Crash With Differing Error Messages

I have a major issue that I am stuck with at work and would REALLY appreciate some help on. This has cost me 2 days already.
What I am trying to do is have a special image class fire it's assigned callback when it's touched. Thats it.
But when I touch the image it crashes, often without an error message just (lldb). Sometimes it says garbage like "[__NSCFData tapped:]: unrecognized selector sent to instance 0x1780af360". Sometimes it says "message sent to deallocated object".
I can run the app 10 times in a row and get one of these random messages just from tapping the same object on the screen each time.
The code is very simple:
//view controller
var k:K_PreviewImage!
override func viewDidLoad()
{
var image:iImage = iImage(imageName: "ja3.jpg")
k = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
And here is the code inside K_PreviewImage (my clickable image). This does NOT inherit from anything, including NSObject
var touchCallback:((K_PreviewImage)->Void)
{
set{
if(_touchCallback == nil)
{
var tap:UIGestureRecognizer = UITapGestureRecognizer(target: self, action:"tapped:")
_image.addGestureRecognizer(tap)
}
_touchCallback = newValue
}
get{
return _touchCallback
}
func tapped(tap:UITapGestureRecognizer)
{
println("here")
if(_touchCallback != nil)
{
touchCallback(self)
}
}
The above code causes a crash 100% of the time.
If I add #objc too the tap event listener in the K_PreviewImage like this
#objc func tapped(tap:UITapGestureRecognizer)
{
println("here")
if(_touchCallback != nil)
{
touchCallback(self)
}
}
Then the code WORKS and the touch event is fired (both inside K_PreviewImage and the controllers callback function 'nestedTap'.
Not sure why that works, but it does. However I'm still up a creek without a paddle because when I make the K_PreviewImage a function variable instead of a class member variable, I get a crash again
override func viewDidLoad()
{
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
}
So my first question is why did I need to add #objc to get the callback to fire instead of giving me an unclear 'deallocated' memory crash?
My second question is why is it when I moved the variable from a member variable to a function variable does it cause the crash all over again. If the object was deallocated it couldnt hear the tap event in the first place could it? Why would it still be on screen? And why is it getting dealloacted just because it's not a member variable!
How the heck can I create a custom object dynamically and have it fire click events!
UPDATE
Here is the full code block involving how my K_PreviewImage was being added with a touch event
var _array:Array<iImage>!
override func viewDidLoad()
{
_array = Array<iImage>()
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
_array.append(k.image)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
Solved:
override func viewDidLoad()
{
_array = Array<K_PreviewImage>()
var image:iImage = iImage(imageName: "ja3.jpg")
var k:K_PreviewImage = K_PreviewImage(image: image)
k.touchCallback = nestedTap
_view.addSubview(k.image)
_array.append(k)
}
func nestedTap(k:K_PreviewImage)
{
println("Successs")
}
Because I was not storing a reference to the K_PreviewImage 'k' even though the subview was added, even though it belongs to k, k was not being retained. By making the array store a reference to K_PreviewImage instead of iImage the compiler now retains a reference to the K_Preview Image
First off, it looks like you're trying to add an image as a subview. I don't know Swift all that well, but _view.addSubview(k.image) looks wrong to me and would make more sense as _view.addSubview(k).
Secondly, you're not keeping your reference to your K_PreviewImage. You are assigning k.image as the subview, which causes k to be detected as "no longer used" by the system. It will clean it up and when you try to access it, you'll crash.

Check if node has actions being processed?

I'm executing the SKAction rotateToAngle on one method. On another method I want to know in whether the action is still being executed or if it has ended.
I could save the time the action started in a property and check it however I was wondering whether there is an easier method.
private func rotate(motionManager: CMMotionManager, gravity: CGVector) {
let rotate = SKAction.rotateToAngle(CGFloat(M_PI * 5), duration: 5.0, shortestUnitArc: true)
self.runAction(rotate)
}
private func actionRunning() --> Bool {
}
I tried using self.hasActions() but it always return true. Any ideas on how to do this on Swift?
You could always use the withKey parameter when using runAction. Then do a actionForKey: method to determine if it still exists.
In Objective-C it looks like this:
[self runAction:[SKAction waitForDuration:0.1] withKey:#"waitTimer"];
[self actionForKey:#"waitTimer"];
I'm not familiar with Swift, but the Apple Docs do show it supported for the Swift Language as well.
https://developer.apple.com/library/prerelease/ios/documentation/SpriteKit/Reference/SKNode_Ref/index.html#//apple_ref/occ/instm/SKNode/runAction:withKey:
func runAction(_ action: SKAction!,
withKey key: String!)

Resources