Looking forward to working through this tutorial when I upgrade to iOS12 and Xcode 10. Tried to run it in iOS 11, Xcode 9 but lotās of errors. Great examples of how to use the new CaseIterable protocol in swift 4.2. Well done Lyndsey.
To run in Xcode 9.4.1, with iOS 11.4, you need to fix 37 errors. All but 2 are about the new CaseIterable protocol for enums.
First error is: iOS Deployment Target ā12.0ā is newer than SDK āiOS 11.4ā in target People Keeper.
If you click the error, it takes you to the setting, which you can change to 11.4.
Second error: UICollectionView.elementKindSectionHeader is used twice in PersonDetailViewController. Change it to UICollectionElementKindSectionHeader.
The other 35 errors: change each of the six enums the way this one is changed:
enum HairColor: String { //}, CaseIterable {
case black = "black", brown = "brown", blonde = "blonde", red = "red", gray = "gray"
static let allCases: [HairColor] = [black, brown, blonde, red, gray]
}
That is, comment out the CaseIterable. Then add a static let allCases, as an array of the enum type, set to an array of all the values. Yes, the Topic one is pretty long. It will be nice to have CaseIterable.
Thatās it. It should now compile and run. I was able to do the entire tutorial, and everything worked.
I would not have gone through the trouble of converting it if I did not think it was worthwhile, so I will now comment on the tutorial as well.
Itās a really excellent tutorial, in several ways, including good examples of code that are not related to undo-redo.
Excellent comments in the code. They are written in complete sentences, often several lines long, and really explain what is going on and what the intentions are. That took time and effort, and is quite valuable.
Very nice job factoring the code throughout the app. It is a good model of how to build something that is robust and flexible at the same time. The phrase ālocal reasoningā is new to me, but I believe it is what I would call āpush as much logic as you can down into the data model.ā
The undo-redo capability suddenly looks not so hard to do. It is a really nice feature to have in an app, the kind of thing users always want. I am sure I will be checking back on my version of this.
I will stop gushing now. I am a bit wary of making everything structs to get value semantics, especially if photos or or other large objects are involved. I am tempted to see if I can get the same result with classes. The refactoring and ālocal reasoningā make it seem like it could still work out well.
Thank you @sgerrard for the hints on how to make this work in Xcode 9 and iOS 11.4. I was able to get the project up and running perfectly in 5 minutes.
I have a question about how the undoManager is declared inside the two ViewControllers in the project:
private let _undoManager = UndoManager()
override var undoManager: UndoManager {
return _undoManager
}
The comments in the code say you need to override the built-in undoManager; but itās not clear to me why. Couldnāt you use the built-in one without re-creating a new instance? Are you disabling any functionality by overriding the built-in one? Why not create a class var called myUndoManager and use that instead?
Thatās exactly what I did in both view controllers. I even used that name.
You only need to override the property if you want a new one with the same name.
I think it is preferable to have a local undoManager, if the actions are all specific to the current view, so they arenāt still there after the view is closed. I donāt think you lose anything doing that. I think the default one is at the App level, and would keep everything, which could get strange.
Thanks for the reply! I found a short snippet on another online article that talked about the scope of the undoManager so that makes complete sense now.
.
And what about when the undo manager is not local to the view? I just posted this question to forums.developer.apple.com so weāll see what answer I get there, but Iāve got a tab bar controller at the top, with a nav controller inside it, with a table view controller inside the nav controller. I want to put the undoManager in the tab bar controller, so that everything changed across all four tabs will go into a single undoManager, and when the tab bar goes away, Iāll write the document back out, if necessary.
But the docs for UIResponder.undoManager seem to be misleading. They say, essentially, that thereās a runtime stack of undoManagers, one for each view in the hierarchy, and when you access UIResponder.undoManager, it walks up the hierarchy until it finds an undoManager to use. They give the example of a UITextField having its own undoManager, separate from the view that contains it (which makes sense). But it doesnāt appear to work that way. UIResponder.undoManager is a computed property, so it certainly could be code that traverses the view hierarchy, but whenever I access it in the table view controller, I get nilā¦
Thank you for checking and determining that it works for you; there must be something wrong with my code setup.
I have canBecomeFirstResponder returning true in my UITabBarController, along with a computed undoManager that returns a private UndoManager object. Then I call beginFirstResponder() in viewDidAppear(). Then it segues to the nav controller which loads a UITableViewController subclass that tries to use undoManager.registerUndo() (this is Swift 4) and thatās where itās nil.
But if it works for you, there must be something else Iām doing wrong.
I did notice that youāre accessing both self.undoManager and view.undoManager. Why both?
The documentation says, āYou may add undo managers to your view controllers to perform undo and redo operations local to the managed view.ā Maybe the word ālocalā is the problem? Perhaps I need to put the undoManager into my view instead? But that means subclassing the UITableViewā¦ Iām guessing you didnāt do that.
Perhaps the UIResponder.undoManager computed property only checks the view hierarchy and undo managers inside a controller are meant to be local to that controller?! That would explain what Iām seeing, but it doesnāt explain why your self.undoManager worked in the view controller pushed onto the navigation controller stack and mine didnāt.
Thanks for the help! Iāll create an empty project and play with it there and see if I can narrow it down. Iāll report back here if/when I get this solved.
Edit: I just did the same test ā Tab Bar ā Nav Controller ā TableView Controller ā in a new project and I got the same results you did, i.e. it worked. Thereās clearly something weird going on elsewhere in my app. I just need to figure out what it is. Iām going to try these same print statements in my app and see what I get. Thanks again for your help!
Edit^2: It seems that the undoManager object does work in viewDidAppear() but not viewWillAppear(). I donāt understand why other methods later in the life cycle canāt seem to find it, but I now believe it does have something to do with the view life cycle. Cheers!
Itās bad form to reply to oneās own post, but I have an answer. The forums @ apple.com provided the details. Essentially, the dynamic lookup of the undoManager field is of views and view controllers that are in the view hierarchy and when I invoke the callback (the closure) on a superclass that is a view, that superclass object is not currently in the view hierarchy. (Apparently, views not visible are not in the hierarchy, although I didnāt find that specifically stated anywhere in the docs.)
This means I can move my call to viewDidDisappear() (meaning the top view is hidden and the layer behind it is visible and thatās where the undoManager is) and it will work, or I could leave it where itās at and pass an undoManager object that is accessible via normal inheritance of properties and is not based on a dynamic lookup in some opaque table whose contents is (apparently) undocumented.
Unfortunately, I want the undoManager object in the nav controller so that all pages that are part of the navigation stack would have access to it. But my view controller classes wonāt be subclassing the nav controller, so inheritance wonāt help either. It appears Iām going to have to implement my own āundoManager stackā and use that.
No promises, but if I find a better solution while implementing that one, Iāll come back here and post about it. Thanks for the discussion.