I have four imageview contents in an XIB and a button that covers all my XIB. I want to make when the user tap the button, the first imageview is shown, the next tap is hidden and the second imageview is displayed and so on until all my imageview is shown / hidden. What would be the most efficient way to do it?
Save all your UIImageViews to an array, and current showing imageView to a variable, it may look like this:
var imageViews: [UIImageView] = []
var currentImageViewIndex = 0 {
didSet {
if currentImageViewIndex >= imageViews.count { currentImageViewIndex = 0 }
imageViews[oldValue].isHidden = true
imageViews[currentImageViewIndex].isHidden = false
}
}
func handleTap() {
currentImageViewIndex += 1
}
I suggest you use a state variable that contains an enum listing the various states (firstImageVisible, secondImage.... ) then you can have a function inside the enum that switches to the nextState (being the target of your button action) you can also easily iterate through states of an enum, check the documentation for the CaseIterable protocol. Often having a property observer (didSet) on the state is a handy place to update other parts of the UI which need to change every time the state changes.
Related
I have a UIPageControl in my app's onboarding process. Its purpose is not to change pages manually but as an indication of the user's process through the whole onboarding. (There's no swiping gestures right now)
Everything looks fine, but VoiceOver lets the user increment or decrement the control, and says it can be changed (it seems to keep .adjustable as a trait). I don't want that behaviour. I just want VoiceOver to read "Page 1 of 3". I disabled it, changed its accessibilityTraits and it doesn't affect VoiceOver.
Here is some code.
/// hard coded values for the example:
pageControl.numberOfPages = 3
pageControl.currentPage = 1
pageControl.isEnabled = false
pageControl.isUserInteractionEnabled = false
pageControl.accessibilityTraits = .none
I have created a test project on github for a more complete example.
One way to get your purpose is to subclass UIpageControl and override the accessibiliTraits property as follows:
class MyPageControl: UIPageControl {
override var accessibilityTraits: UIAccessibilityTraits {
get{
return .none
}
set{}
}
}
Define your pageControl element as MyPageControl to get the desired result.
I have a fairly simple set up in my main storyboard:
A stack view which includes three views
The first view has a fixed height and contains a segment controller
The other two views have no restrictions, the idea being that only one will be active at a time and thus fill the space available
I have code that will deal with the changing view active views as follows:
import Foundation
import UIKit
class ViewController : UIViewController {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var segmentController: UISegmentedControl!
#IBAction func SegmentClicked(_ sender: AnyObject) {
updateView(segment: sender.titleForSegment(at: sender.selectedSegmentIndex)!)
}
override func viewDidLoad() {
updateView(segment: "First")
}
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}
}
}
As you can see, when the tab called 'First' is selected, the subview at index 1 should show, whilst 2 is hidden, and when anything else is selected, the subview at index 2 should show, whilst 1 is hidden.
This appears to work at first, if I go slowly changing views, but if I go a bit quicker, the view at index 1 seems to remain permanently hidden after a few clicks, resulting in the view at index 0 covering the whole screen. I've placed an animation showing the issue and a screenshot of the storyboard below. The output shows that when the problem happens, both views remain hidden when clicking on the first segment.
Can anybody tell me why this is happening? Is this a bug, or am I not doing something I should be?
Many thanks in advance!
Update: I seem to be able to reliably reproduce the issue by going to the First > Second > Third > Second > First segments in that order.
The bug is that hiding and showing views in a stack view is cumulative. Weird Apple bug. If you hide a view in a stack view twice, you need to show it twice to get it back. If you show it three times, you need to hide it three times to actually hide it (assuming it was hidden to start).
This is independent of using animation.
So if you do something like this in your code, only hiding a view if it's visible, you'll avoid this problem:
if !myView.isHidden {
myView.isHidden = true
}
Building on the nice answer by Dave Batton, you can also add a UIView extension to make the call site a bit cleaner, IMO.
extension UIView {
var isHiddenInStackView: Bool {
get {
return isHidden
}
set {
if isHidden != newValue {
isHidden = newValue
}
}
}
}
Then you can call stackView.subviews[someIndex].isHiddenInStackView = false which is helpful if you have multiple views to manage within your stack view versus a bunch of if statements.
In the end, after trying all the suggestions here I still couldn't work out why it was behaving like this so I got in touch with Apple who asked me to file a bug report. I did however find a work around, by unhiding both views first, which solved my problem:
func updateView(segment: String) {
UIView.animate(withDuration: 1) {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = false
if(segment == "First") {
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
}
}
}
Based on what I can see, this weird behavior is caused by the animation duration. As you can see, it takes one second for the animation to complete, but if you start switching the segmentControl faster than that, then I would argue that is what is causing this behavior.
What you should do is deactivate the user interactivity when the method is called, and then re-enable it once the animation is complete.
It should look something like this:
func updateView(segment: String) {
segmentControl.userInteractionEnabled = false
UIView.animateWithDuration(1.0, animations: {
if(segment == "First") {
self.stackView.arrangedSubviews[1].isHidden = false
self.stackView.arrangedSubviews[2].isHidden = true
} else {
self.stackView.arrangedSubviews[1].isHidden = true
self.stackView.arrangedSubviews[2].isHidden = false
}
print("Updating views")
print("View 1 is \(self.stackView.arrangedSubviews[1].isHidden ? "hidden" : "visible")")
print("View 2 is \(self.stackView.arrangedSubviews[2].isHidden ? "hidden" : "visible")")
}, completion: {(finished: Bool) in
segmentControl.userInteractionEnabled = true
}
}
While this will prevent from fast switching (which you may see as a downside), the only other way I am aware of that solve this is by removing the animations altogether.
Check the configuration and autolayout constraints on the stack view and the subviews, particularly the segmented control.
The segmented control complicates the setup for the stack view, so I'd take the segmented control out of the stack view and set its constraints relative to the main view.
With the segmented control out of the stack view, it's relatively straightforward to set up the stack view so that your code will work properly.
Reset the constraints on the stack view so that it is positioned below the segmented control and covers the rest of the superview. In the Attributes Inspector, set Alignment to Fill, Distribution to Fill Equally, and Content Mode to Scale to Fill.
Remove the constraints on the subviews and set their Content Mode to Scale to Fill.
Adjust the indexing on arrangedSubviews in your code and it should work automagically.
I have an app with some buttons, when those buttons are pressed the image on them should change. I assume that the TouchUpInside runs when you tap and remove the finger while still holding inside the area of the element, however it only works rarely and I'm not sure why.
The reason I use TouchUpInside instead of TouchDown is because I want the user to be able to cancel the action.
I'm sorry if I've misunderstood anything about those events and if this has already been asked. I couldn't find an answer to my problem searching the web.
//The IBAction is set to trigger on TouchUpInside
#IBAction func action11(sender: UIButton) {
setTile(sender)
}
func setTile(sender: UIButton) {
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
}
EDIT: Added the necessary code
There are some properties of UIButtons which you can use to achieve what you want.
You can use Default and selected state of uibutton to set two different images.
In XIB select state "Default" and assign default image to that state again select state to "Selected" and assign image which you want after button section.
and add following line in button selection method.
-(IBAction)buttonTapped:(UIButton *)sender{
sender.selected = !sender.selected;
}
Your understanding is correct, you need to use touchUpInside.
I assume you are trying to create a button that has a toggle function. On one touch you want the button to have the value Say "X" and when touched again the button has a value "O".
Take a look at this code below, this should do the job.
class ViewController: UIViewController {
var isButtonPressed = false{
// Adding a Property Observer, that reacts to changes in button state
didSet{
if isButtonPressed{
// Set the Value to X.
}else{
// Set the Value to O.
}
}
}
#IBAction func changeButtonValue(sender: UIButton) {
// Toggle the button value.
isButtonPressed = !isButtonPressed
}
}
If you don't set turn=true after the first time, this code is executed it will be executed only one.
if turn {
print("O's turn")
sender.setImage(xTile, forState: .Normal)
turn = false
}
Check if the button frame is large enough to get finger touch.
Apple says at least 35x35 pixel.
I have created 6 UIImageViews on a ViewController, and I am later going to add TapGestureRecognizers to all of them.
I want to make it so that depending on what image has been clicked, another ViewController will open and display certain information.
For this to happen, I need to know which image has been clicked. How would I do this in Swift?
UIGestureRecognizer has property 'view' this property is the view you add it to. For this example the imageView.
func tap(gesture: UIGestureRecognizer) {
println(gesture.view!.tag) // You can check for their tag and do different things based on tag
}
let img = UIImageView()
img.userInteraction = true
img.tag = 0
img.addGestureRecognizer(UITapGestureRecognizer(self, action: "tap:"))
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)
}
}