The problem I'm trying to solve is this: I have a DetailViewController
that displays the data for my Model with UIImageView
's, UITextFields
's, etc.
If the user taps a button, those DetailViewController
's views move to different positions and start to be editable. When editable, if the user taps one of the UITextField
(just one of them is special) the UITextField
moves to the top of the screen and a UITableView
appears to autocomplete it (just like when you type something on google).
The user can also tap the same button to go back to the display state (where nothing is editable).
So basically I have some views in a ViewController
with 3 possible state: DisplayState
, EditingState
, EditingWithFocusOnSpecialTextFieldsState
.
I'd like to have all those positioning state described by NSLayoutConstraints
, and, if possible, just in the storyboard.
One thing I could do is this Animate to a storyboard state / position, but this involves writing every constraint for each state in code, therefore I couldn't visualize them really well in storyboard while developing (Also, writing constraints in code is a lot less maintainable than in storyboard).
What I would like is something like creating 3 different XIBs, for example, or different copies of my DetailViewController
in storyboard with the 3 different positions for each of the subviews, and then animate between them.
If it makes any difference, I'm always using the latest iOS version (iOS 8 right now) and Swift.
I do know Objective-C very well too if you don't want to answer in Swift.
As far as I know, there's no way to do this with 3 different views, and get the result you want (at least no straight forward way). One approach would be to make 3 constraints (one for each state) to any one edge of the superview that you need to adjust for each view (in addition to any other constraints you need that you're not going to modify). One would have a high priority (I'm using 900, it can't be 1000), and the other 2 would have a lower priority (499 in my example). When you switch states, you change which of the 3 has the high priority. This does involve making a lot of constraints, and I found that the easiest way to implement the switching in code was to give the constraints identifiers in IB (which you do in the "User Defined Runtime Attributes" section of the Identity Inspector). This approach means I don't have to make IBOutlets for all those constraints. Here is an example with two views in the storyboard,
You can see the text field has 3 constraints to the top, and the image view has 3 centerY constraints (though you can't see that there are 3). Each constraint (in the groups of 3) has its identifier set to "display", "edit", or "focus". I give the one that's called "display" the priority of 900, and the other 2 get 499 (because I want the view to start in display mode). In this test app, I'm using 3 buttons to change the state, though, of course, you could use other means to accomplish that. Here is the code I use to switch the priorities,
enum EditState: String {
case Display = "display" // these strings are the same as the ones assigned to the identifier property of the constraints in IB (in "user defined runtime attributes")
case Editing = "edit"
case EditWithFocus = "focus"
}
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
func updatEditingState(state: EditState) {
var constraintsArray = self.view.constraints() as [NSLayoutConstraint]
constraintsArray += self.imageView.constraints() as [NSLayoutConstraint]
for con in constraintsArray {
if let name = con.identifier? {
if name == "display" || name == "edit" || name == "focus" {
con.priority = (name == state.rawValue) ? 900 : 499
}
}
}
UIView.animateWithDuration(0.5) {self.view.layoutIfNeeded()}
}
@IBAction func EnterEditingState(sender: UIButton) {
updatEditingState(EditState.Editing)
}
@IBAction func enterDisplayStatus(sender: UIButton) {
updatEditingState(EditState.Display)
}
@IBAction func enterFocusStatus(sender: UIButton) {
updatEditingState(EditState.EditWithFocus)
}
}