UISwitch not working properly in swift - ios

I have taken a UISwitch control in my new swift project. And the default state of this is set as Off from storyboard. I have bounded it as IBOutlet
#IBOutlet weak var SwitchRemFacility: UISwitch!
I have also bounded it to a method by Value change event from storyboard.
#IBAction func rememberFacilityClick(sender: AnyObject) {
print(SwitchRemFacility.on)
if SwitchRemFacility.on {
SwitchRemFacility.setOn(false, animated: true)
} else {
SwitchRemFacility.setOn(true, animated: true)
}
}
The Problem is that SwitchRemFacility.on returns always true here. I have checked by breakpoint and also I have printed it to console. Any hep will be appreciated.
Thanks in advance.

You are testing it when value is already changed. Switch will do it's on and off work by it self.
If you want to do it manually you can do like :
#IBAction func switchChangeVal(sender: AnyObject) {
let swh : UISwitch = sender as! UISwitch
if(swh.on){
swh.setOn(true, animated: true)//But it will already do it.
}
else{
swh.setOn(false, animated: true)
}
}
Actually no need of code. But if you want to condition on the on or off state then you have to do code.
For Example
#IBAction func switchChangeVal(sender: AnyObject) {
let swh : UISwitch = sender as! UISwitch
if(swh.on){
//Do the code for when switch is on
}
else{
//Do the code for when switch is off
}
}

Related

isHidden or view.removeFromSuperView are taking a long while

I'm trying to show a UITableView from a button, I tried 2 different ways:
One by showing by a UIStackView with was hidden from the beginning, and just show with "isHidden", another way with a view from another UIViewController, which is called with "didMove(toParentViewController: self)"
With both ways, the tableview is showing instantly, but to hide it again, that's taking forever.
I've tried to put the "isHidden = true" in "DispatchQueue.main.sync" to use the main thread, but still not working as I would like...
Any suggestions?
Here is some code of my application :
#IBAction func ProfilPicture1Pressed(_ sender: UIButton) {
let popUpTeamDog = storyboard?.instantiateViewController(withIdentifier: "selectTeamDogPopUp") as! SelectTeamDogPopUp
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
}
})
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let dog: Dog
if(searching){
dog = searchArray[indexPath.row]
} else {
dog = dogs[indexPath.row]
}
let nameDog = dog.name
//let imgDog = UIImage(named: dog.image)
print(nameDog)
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.view.removeFromSuperview()
}
})
So I can add that when I click on the searchBar, then select a dog, the tableView / View disappear instantly, so when the keyboard is activate, that's working well... Stranger things....
Your code uses main thread by default. So you don't have to use this DispatchQueue.global() and DispatchQueue.main. It's too slow.
So replace this
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
}
})
with
self.addChildViewController(popUpTeamDog)
popUpTeamDog.view.frame = self.view.frame
self.view.addSubview(popUpTeamDog.view)
popUpTeamDog.didMove(toParentViewController: self)
and do the same with self.view.removeFromSuperview(). Replace this
DispatchQueue.global().async(execute: {
DispatchQueue.main.sync{
self.view.removeFromSuperview()
}
})
with
self.view.removeFromSuperview()

Swift: Failing to set a [String] through a segue using the sender

Good day. I'm creating my first own app and have ran into an issue. I have a AR scene with clickable stuff which when you touch them a segue triggers into a ViewController and sets that view controllers labels and textview depending on what was touch on screen.
Explanations: 1. CaseViewController is the target view controller. 2. the "artNews" and "politicalNews" are string arrays in which I've written 3 strings, they are defined and are never nil.
Question: I get a crash due to the segueInputText being nil. Why does it become nil and how do I correct it?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! CaseViewController
destinationVC.segueInputText = sender as? [String]
print("\(String(describing: sender))")
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touchLocation = touches.first?.location(in: sceneView),
let hitNode = sceneView?.hitTest(touchLocation, options: nil).first?.node,
let nodeName = hitNode.name
else { return }
if nodeName == imageNameArray[0] {
performSegue(withIdentifier: "CaseViewController", sender: artNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
if nodeName == imageNameArray[1] {
performSegue(withIdentifier: "CaseViewController", sender: politicalNews)
} else {
print("Found no node connected to \(nodeName)")
return
}
the CaseViewController has UILabels and UITextViews connected and this:
var segueInputText : [String]? {
didSet {
setupText()
}
}
func setupText() {
// Why are these values nil?
testLabel.text = segueInputText![0]
ingressLabel.text = segueInputText![1]
breadLabel.text = segueInputText![2]
testLabel.reloadInputViews()
ingressLabel.reloadInputViews()
breadLabel.reloadInputViews() //breadLabel is a UITextView
}
Thank you for reading my question!
Kind regards.
Remove didSet block as when you set the array inside prepare , observe triggers and the lbl is still nil
OR
func setupText() {
if testLabel == nil { // if one is nil then all of them too
return
}
}
Don't use the didSet observer in this case. It will never work.
In setupText() IBOutlets are accessed which are not connected yet at the moment prepare(for is called.
Remove the observer
var segueInputText : [String]?
and call setupText in viewWillAppear
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
setupText()
}
At the very moment when you do this:
destinationVC.segueInputText = sender as? [String]
the destination view controller has not yet loaded hence none of the outlets are connected so accessing any of them will crash your app as they are still nil.
You will have to assign any of the values you’d like to pass to the destination controller to a property and assign this property’s value to the the corresponding outlet in viewDidLoad. This way you make sure all outlets have connected.
For the same reason don’t use a property observer to assign the property’s value to any of the labels as this would happen, again, before the view controller had a chance to load…

iOS Swift - How to assign a default action to all buttons programmatically

I'm working with an app in prototype stage of development. Some interface elements do not have any action assigned to them either through storyboard or programmatically.
According to UX guidelines, I want to find these "inactive" buttons in app and have them display a "feature not available" alert when tapped during testing. Can this be done through an extension of UIButton?
How can I assign a default action to UIButton to show an alert unless another action is assigned via interface builder or programmatically?
Well what you are trying to achieve can be done. I have done this using a UIViewController extension and adding a closure as the target of a button which does not have a target. In case the button does not have an action an alert is presented.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.checkButtonAction()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
#IBAction func btn_Action(_ sender: UIButton) {
}
}
extension UIViewController{
func checkButtonAction(){
for view in self.view.subviews as [UIView] {
if let btn = view as? UIButton {
if (btn.allTargets.isEmpty){
btn.add(for: .touchUpInside, {
let alert = UIAlertController(title: "Test 3", message:"No selector", preferredStyle: UIAlertControllerStyle.alert)
// add an action (button)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
// show the alert
self.present(alert, animated: true, completion: nil)
})
}
}
}
}
}
class ClosureSleeve {
let closure: ()->()
init (_ closure: #escaping ()->()) {
self.closure = closure
}
#objc func invoke () {
closure()
}
}
extension UIControl {
func add (for controlEvents: UIControlEvents, _ closure: #escaping ()->()) {
let sleeve = ClosureSleeve(closure)
addTarget(sleeve, action: #selector(ClosureSleeve.invoke), for: controlEvents)
objc_setAssociatedObject(self, String(format: "[%d]", arc4random()), sleeve, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
}
I have tested it. Hope this helps. Happy coding.
As you cannot override methods in extensions, the only remaining options are:
1. subclass your buttons - but probably not what you're looking for, because I assume you want to use this functionality for already existing buttons
2. method swizzling - to change the implementation of existing function, i.e. init
How can I assign a default action to UIButton to show an alert unless
another action is assigned via interface builder or programmatically?
I would suggest to do method swizzling:
Through swizzling, the implementation of a method can be replaced with
a different one at runtime, by changing the mapping between a specific
selector(method) and the function that contains its implementation.
https://www.uraimo.com/2015/10/23/effective-method-swizzling-with-swift/
Remark: I would recommend to check the article above.
As an exact answer for your question, the following code snippet should be -in general- what are you trying to achieve:
class ViewController: UIViewController {
// MARK:- IBOutlets
#IBOutlet weak var lblMessage: UILabel!
// MARK:- IBActions
#IBAction func applySwizzlingTapped(_ sender: Any) {
swizzleButtonAction()
}
#IBAction func buttonTapped(_ sender: Any) {
print("Original!")
lblMessage.text = "Original!"
}
}
extension ViewController {
func swizzleButtonAction() {
let originalSelector = #selector(buttonTapped(_:))
let swizzledSelector = #selector(swizzledAction(_:))
let originalMethod = class_getInstanceMethod(ViewController.self, originalSelector)
let swizzledMethod = class_getInstanceMethod(ViewController.self, swizzledSelector)
method_exchangeImplementations(originalMethod, swizzledMethod)
}
func swizzledAction(_ sender: Any) {
print("Swizzled!")
lblMessage.text = "Swizzled!"
}
}
After calling swizzleButtonAction() -by tapping the "Apply Swizzling" button (applySwizzlingTapped)-, the selector of "Button" should changed from buttonTapped to swizzledAction.
Output:
I would implement this kind of function by using IBOutlet collection variable, and then removing buttons from that collection as soon as the feature is implemented. something like this:
class ViewController {
#IBOutlet var inactiveBtns: [UIButton]!
func viewDidLoad() {
inactiveBtns.forEach { (button) in
button.addTarget(self, action: #selector(showDialog), for: UIControlEvents())
}
}
func showDialog() {
// show the dialog
}
}
That IBOutlet is visible in the interface builder, so you can add multiple buttons in there.

UISwitch set state

I am working on an app, which has UISwitch. I want to set the switch to on/off programmatically. I tried it like so, but the switch is already turned on when setting is false... Any ideas?
#IBOutlet var switch: UISwitch!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
switch = UISwitch()
if setting == true {
switch.setOn(true, animated: false)
} else {
switch.setOn(false, animated: false)
}
}
the variable setting is not the problem.. I tested it out
I fixed it!
I just deleted the line switch = UISwitch() and now it works for me.

How to know the sender's identifier in Swift

I have two UILabels with two UITapGestureRecognizers in a UITableViewCell.
cell.Username.tag = indexPath.row
cell.SharedUser.tag = indexPath.row
let tapGestureRecognizer2 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
let tapGestureRecognizer3 = UITapGestureRecognizer(target:self, action:"GoToProfil:")
cell.Username.userInteractionEnabled = true
cell.Username.addGestureRecognizer(tapGestureRecognizer2)
cell.SharedUser.userInteractionEnabled = true
cell.SharedUser.addGestureRecognizer(tapGestureRecognizer3)
func GoToProfil (sender: AnyObject!) {
self.performSegueWithIdentifier("GoToProfilSegue", sender: sender)
}
I'm using a Segue to push another UIViewController, and I'm overriding the PrepareSegue function to send the needed information corresponding to the Sender tag.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
let ProfilView = segue.destinationViewController as! Profil
ProfilView.hidesBottomBarWhenPushed = true
ProfilView.title = posts[sender.view!.tag].User?.objectForKey("Name") as? String
ProfilView.User = posts[sender.view!.tag].User
}
My problem is that I want to know which UILabel was pressed, knowing that I'm already using tag.
Your GoToProfile: function should be written properly. The parameter isn't the "sender", it's the gesture recognizer.
func GoToProfil (gestureRecognizer: UITapGestureRecognizer) {
}
From there, you can determine the label by using the view property of the gesture recognizer.
But you seem to have two conflicting requirements. You want to know which of the two labels was tapped and you want to know which row the label is in.
Normally you would use the label's tag to know which of the two labels was tapped. But you are using their tags to track the row.
The solution I recommend is to use the tag to differentiate the two labels. Then you can calculate the row based on the frame of the label.
See the following answer for sample code that translates the frame of a cell's subview to the cell's indexPath.
Making the following assumptions:
You are trying to uniquely identify the label using UIView.tag
You want different behaviour for Username & SharedUser
I recommend the following, first define your tags below your #imports
#define kUsername 1
#define kSharedUser 2
Then assign them to your views
cell.Username.tag = kUsername
cell.SharedUser.tag = kSharedUser
Then in your prepareSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
int tag = [sender.view!.tag]
if (tag == kUsername) {
//Username logic
} else if(tag == kSharedUser) {
//Shared User Logic
}
}
This way you can easily and simply determine tap, Note this might have different results if you have more then 1 Username & SharedUser labels. Then you will either need more #defines or change how you generate your tags.
You can add a property to UILabel to track the label's type. (I used an enum since there's just 2 cases, but it could be a string, etc.)
enum LabelDest : String
{
case Username = "Username"
case SharedUser = "SharedUser"
}
extension UILabel
{
struct Static {
static var key = "labelDest"
}
var labelDest:LabelDest? {
set { objc_setAssociatedObject( self, &Static.key, newValue?.rawValue, .OBJC_ASSOCIATION_COPY_NONATOMIC )
}
get {
guard let val = objc_getAssociatedObject( self, &Static.key ) as? String else { return nil }
return LabelDest( rawValue:val )
}
}
}
Now you can just do this:
let label = UILabel()
label.labelDest = .Username
Later:
switch label.labelDest
{
case .Some(.Username):
// handle user name
break
...
If you want to use the .tag field on your labels you can use a different technique to find the table row associated with a label: (again using class extensions)
extension UIView
{
var enclosingTableViewCell:UITableViewCell? {
return superview?.enclosingTableViewCell
}
var enclosingTableView:UITableView? {
return superview?.enclosingTableView
}
}
extension UITableViewCell
{
var enclosingTableViewCell:UITableViewCell? {
return self
}
}
extension UITableView
{
var enclosingTableView:UITableView? {
return self
}
}
extension UIView {
var tableRow:Int? {
guard let cell = self.enclosingTableViewCell else { return nil }
return self.enclosingTableView?.indexPathForCell( cell )?.row
}
}
Now, from your gesture recognizer action:
func goToProfil( sender:UIGestureRecognizer! )
{
guard let tappedRow = sender.view?.tableRow else { return }
// handle tap here...
}
You can access the sender data, and read the tag of the object that send you, like in this sample code.
To uniquely identify each row and each label, you can use something like this:
cell.Username.tag = (indexPath.row*2)
cell.SharedUser.tag = (indexPath.row*2)+1
With this, if you have a even tag, its the Username, odd will be the SharedUser. Dividing by the floor of 2 you can have the row back.
#IBOutlet weak var test1: UILabel!
#IBOutlet weak var test2: UILabel!
override func viewWillAppear(animated: Bool) {
test1.tag = 1
test2.tag = 2
test1.userInteractionEnabled = true
test2.userInteractionEnabled = true
self.test1.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
self.test2.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "handleSingleTap:"))
}
func handleSingleTap(sender: UITapGestureRecognizer) {
print(sender.view?.tag)
}

Resources