IOS Swift 4 UIView isHidden with tapGestureRecognizer - ios

I got a weird problem.
I made a simple Single View App with a TabBarController, added Subviews to the first Item (ViewController) and made them hidden with the interface builder properties.
Now I want to recognize taps within the entire view to make them visible one after another and hide them again in the end.
The gestureRecogniser is placed from the interface builder. All outlets are properly connected and do work.
Anyway, when I tap the first time the UIViews won't show but the isHidden property is set correctly. I've tried many things and searched SO but nothing really helped in this case. Force-Wrapping everything into the main thread doesn't help either and shouldn't make a difference in this case anyway.
This is my code:
//
// ViewController.swift
//
import UIKit
class ViewController: UIViewController {
var step = 0
#IBOutlet weak var firstViewOutlet: UIView! // properly connected
#IBOutlet weak var secondViewOutlet: UIView! // properly connected
#IBOutlet weak var thirdViewOutlet: UIView! // properly connected
#IBOutlet var tapRecognizer: UITapGestureRecognizer! // properly connected
override func viewDidLoad() {
super.viewDidLoad()
self.tapRecognizer.addTarget(self, action: #selector (self.tapAction(_:)))
print("viewDidLoad")
// Tried to hide them manually on load. Didn't work so I switched back to the interface builder property (checkbox)
// hideSubviews()
}
override func viewDidLayoutSubviews() {
print("viewDidLayoutSubviews")
}
#objc func tapAction(_ sender:UITapGestureRecognizer){
print("Tapped: \(self.step)") // Always works and shows the correct step value
switch self.step {
case 0:
print("Untap should show! \(self.step)") // Shows after first tap
DispatchQueue.main.async {
self.firstViewOutlet.isHidden = false
print("Value of first view state: \(self.firstViewOutlet.isHidden)") // shows false but visible after SECOND tap
self.step += 1
}
// Alternative doesn't change anything
// self.firstViewOutlet.isHidden = false
case 1:
print("Upkeep should show! \(self.step)") // Shows second first tap
DispatchQueue.main.async {
self.secondViewOutlet.isHidden = false
print("Value of second view state: \(self.secondViewOutlet.isHidden)") // shows false but visible after THIRD tap
self.step += 1
}
// Alternative doesn't change anything
// self.secondViewOutlet.isHidden = false
case 2:
print("Draw should show! \(self.step)") // Shows after third tap
DispatchQueue.main.async {
self.thirdViewOutlet.isHidden = false
print("Value of third view state: \(self.thirdViewOutlet.isHidden)") // shows false but visible after FOURTH tap
self.step += 1
}
// Alternative doesn't change anything
// self.thirdViewOutlet.isHidden = true
case 3:
print("Draw should clear! \(self.step)")
hideSubviews() // works after FIFTH tap
// Alternative doesn't change anything
// self.firstViewOutlet.isHidden = true
// self.secondViewOutlet.isHidden = true
// self.thirdViewOutlet.isHidden = true
// self.step = 0
default:
return
}
}
func hideSubviews(){
DispatchQueue.main.async {
self.thirdViewOutlet.isHidden = true
self.firstViewOutlet.isHidden = true
self.secondViewOutlet.isHidden = true
self.step = 0
}
}
}
Funny enough, after the first cicle everything works as espected.
I even tried to add view.layoutIfNeeded(), view.layoutSubviews(), view.setNeedsDisplay() and view.setNeedsLayout() to the outlets and to self.view but it didn't change anything either.
Any suggestions are appreciated. I bet it's a small and stupid bug that I just can't see.
It seems it's any UI change. Even changing the labels colors doesn't work on the first tap.
Steps to reproduce:
Create a new project (Single View Application).
Click ViewController, Editor->Embed in->Tap Bar Controller
Click Library icon, drag Container view to ViewController
Set [hidden] property for this Container view to true in Interface Builder
Create Outlet from Container View to ViewController
Click Library icon, drag Tap Gesture Recognizer to ViewController (not into container view!)
Create Outlet from Container View to ViewController
Copy my code or write from scratch

Related

ViewController or UIView should be in control?

So I have a 'ListViewController' that has a subview called 'ListView'
ListView is the master view that holds all remaining subviews.
Inside listView, there is a subview called PhotoView and inside it, it has a UIButton that toggles the constraints of PhotoView, resizing based on arbitrary values.
Hierarchy is as follows:
ListViewController <- ListView <- PhotoView
The reason I've done this is to minimize the amount of code inside each view, to segment it out.
My question is: Who should be in charge of the 'resizing' function? My
understanding is that a viewController should generally handle the
main functions that are inside it pertaining to its subviews.
However in this case, it is being handled inside its subviews subview, 'PhotoView'. Should I use protocols to persist it to the ListViewController? PhotoView(Protocol) -> ListView(Protocol) -> ListViewController?
Is this a correct method? Or is there a more efficient way of handling subview functions?
Thanks guys
If you move all logics into ListViewController, everything is very simple as you can assign any UIControl in the VC:
class ListViewController : UIViewController{
#IBOutlet var goButton: UIButton! //In the DetailView
#IBOutlet var photoView: PhotoView!
#IBOutlet var detailView: DetailView!
// MARK: --
#IBAction func testCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
// MARK: --
#IBAction func photoViewCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
// MARK: --
#IBAction func detailViewCommand(_ sender: Any){
PhotoView.backgroundColor = UIColor.red
detailView.backgroundColor = UIColor.red
}
}
You don't need to even know the ListView. Use IB can assign all UIViews , not only direct children, but all subview trees, including UIControls, like buttons and labels , as long as they lies in the scene of same UIViewController and their methods is in the same VC not in other separated views.
If you have the code of views, you can move it to the VC easily. Just one class to control all. If you need to know who is who. Adding Mark is enough for small files.

IOS Segmented Control: Can't Change Selected Segment

I created a segmented control in my storyboard that looks like this:
Then I created an IBAction for when the control's value changes:
#IBAction func segmentValChanged(_ sender: UISegmentedControl) {
print("touched")
if (sender.selectedSegmentIndex == 0) {
sender.selectedSegmentIndex = 1;
}
else{
sender.selectedSegmentIndex = 0;
}
}
The only issue is that this function is never called!
When I click the Jokes (Beta) button, nothing happens.
However, when I created an IBOutlet for the control and tried to change the selected segment in my viewDidLoad(), it worked:
segmentedController.selectedSegmentIndex = 1
I know I probably missed something obvious, since I've never used a segmented control before.
Thanks so much if anyone can point out what this is.
Cheers!
import UIKit
import Alamofire
class ViewController: UIViewController {
var currentImage: CurrentImage!
var parameters: Parameters!
#IBOutlet weak var segmentedController: UISegmentedControl!
#IBAction func segmentValChanged(_ sender: UISegmentedControl) {
print("touched")
if (sender.selectedSegmentIndex == 0) { // switch to 1
sender.selectedSegmentIndex = 1;
}
else{
sender.selectedSegmentIndex = 0;
}
}
override func viewDidLoad() {
super.viewDidLoad()
segmentedController.selectedSegmentIndex = 1
self.segmentedController.addTarget(self, action: #selector(segmentValChanged(_:)), for: .valueChanged)
}
}
And this is what it looks like when I right click the view:
Whenever I see this it is almost always the same problem... If the segmented control is being drawn outside of its parent view, then it will not respond to taps. This is most likely the problem you are having. (This can also happen with UIButtons.)
Ways to test if this is the problem:
set clipsToBounds of the segmented control's parent to true. If the control no longer shows on the screen, then it is being drawn outside of it's parent view. segmentedControl.superview?.clipsToBounds = true
add a border to the segmented control's parent. If the control is being drawn outside the border then there you go. segmentedControl.superview?.layer.borderWidth = 1
Solve the problem by expanding the size of the parent view so the control is being drawn within it.
On a side note, your IBAction is incorrect. When the value changed action is called, the segmented control will already have changed its value and the new segment will be highlighted. You don't have to do it manually. This is another indicator that your control isn't being drawn in the bounds of its parent view.

Check UISwitch status on viewDidLoad

I am having problem with status of UISwitch at start. Why is my switch always in the on state?
#IBOutlet weak var switch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
if let sw = switch {
if sw.on {
print("on")
} else {
print("off")
}
}
}
The switch will be whatever you set it in the storyboard / interface builder. If you set it to on in the storyboard, it will always be on when the app first loads, and vice versa for setting it to off.
If you want to set it to off when your view first loads (programatically), regardless of what is set in the storyboard, use switch.on = false
If you want the switch's state to be saved when the app is closed, you should look into using NSUserDefaults (here)
Here is a screenshot of the state set in the storyboard:

I have a uiviewcontroller with 2 containers. How can I distinguish from the code in swift which one currently is visible?

I have one UIViewController with two containers. Each of those containers contains one UIViewController. On the parent view controller I have the segmented control that lets user decide which controller should be currently visible:
#IBOutlet weak var firstView: UIView!
#IBOutlet weak var secondView: UIView!
#IBAction func indexChanged(sender: UISegmentedControl) {
switch segmentedControl.selectedSegmentIndex {
case 0:
firstView.hidden = true
secondView.hidden = false
case 1:
firstView.hidden = false
secondView.hidden = true
default:
break;
}
}
Now, in each of the two embedded UIViewControllers I have a viewDidLoad function:
override func viewDidLoad(){
print("first container")
and:
override func viewDidLoad(){
print("second container")
and my problem is: when user enters the parent view controller he immediately sees in the console:
first container
second container
even though only the first container is visible and the second one is not.
I would like to distinguish which container is currently visible, so that when user enters the parent view controller and sees the first container, he should see only first container in the console. And after hitting the segmented control he should see second container, and so forth.
I tried to add those prints in viewWillAppear but they both load at the same time. How should I modify my code to achieve such effect?

Click button in a UIViewController that was loaded a subView (UIView)

I'm trying to add a UIView subview into a UIViewController, and that UIView has a UISwitch that I want the user to be able to toggle. Based on the state, a UITextField's value will toggle back and forth. Here is the subview (InitialView):
import UIKit
class InitialView: UIView {
// All UI elements.
var yourZipCodeSwitch: UISwitch = UISwitch(frame: CGRectMake(UIScreen.mainScreen().bounds.width/2 + 90, UIScreen.mainScreen().bounds.height/2-115, 0, 0))
override func didMoveToSuperview() {
self.backgroundColor = UIColor.whiteColor()
yourZipCodeSwitch.setOn(true, animated: true)
yourZipCodeSwitch.addTarget(ViewController(), action: "yourZipCodeSwitchPressed:", forControlEvents: UIControlEvents.TouchUpInside)
self.addSubview(yourZipCodeSwitch)
}
}
If I want to have it's target properly pointing at the below function, where should I either set the target or include this function? I tried:
Setting the target in the UIViewController instead of the UIView
Keeping the function in the UIView
Here's the function:
// Enable/disable "Current Location" feature for Your Location.
func yourZipCodeSwitchPressed(sender: AnyObject) {
if yourZipCodeSwitch.on
{
yourTemp = yourZipCode.text
yourZipCode.text = "Current Location"
yourZipCode.enabled = false
}
else
{
yourZipCode.text = yourTemp
yourZipCode.enabled = true
}
}
And here is where I'm loading it into the UIViewController:
// add initial view
var initView : InitialView = InitialView()
// Execute on view load
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
view.addSubview(initView)
}
Any help is much appreciated - thanks!
Yeah, the didMoveToSuperView() placement doesn't make much sense. So you're creating a random, totally unconnected ViewController instance to make the compiler happy but your project sad. Control code goes in controllers, view code goes in views.
You need in your real ViewController:
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(initView)
// Note 'self' is the UIViewController here, so we got the scoping right
initView.yourZipCodeSwitch.addTarget(self, action: "yourZipCodeSwitchPressed:", forControlEvents: .ValueChanged)
}
Also, .TouchUpInside is for UIButtons. Toggle switches are much more complicated, so their events are different. Touching up inside on a toggle switch's current setting can and should do nothing, whereas touchup inside on the opposite setting triggers the control event above. iOS does all the internal hit detection for you.

Resources