UIViewController & UITableViewController - Settings Page - ios

I've made a little app on the UIViewController and I want it to have a settings page.
I made a button on it which leads to a UITableView Controller, so far all I have on it is a toggle for Vibrate but when I turn the switch off, go to main, and return back the switch is reset to original position ('on').
How can I make it so the state stays and where do I define the action for the toggle?

You should at start, load the UI with the settings current values.
In the viewWillAppear or even in viewDidLoad, you can just set the options, it will make the view shows the data as is set.

I make a simple project to demonstrate how to save the switch state. Here's the sample code and you can download the whole project here: https://drive.google.com/file/d/0B2_OK50NaRBpVVE4ZzhORUJJaG8/view?usp=sharing
class ViewController: UIViewController {
#IBOutlet weak var mySwitch: UISwitch!
#IBOutlet weak var stateLabel: UILabel!
let defaults = NSUserDefaults.standardUserDefaults()
let savedKey = "switch_state"
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Clear", style: .Plain, target: self, action: "clearSettings")
mySwitch.addTarget(self, action: "toggleSwitch", forControlEvents: .ValueChanged)
// Retrieve data from NSUserDefaults & update switch state and label
let switchState = defaults.boolForKey(savedKey)
mySwitch.setOn(switchState, animated: true)
self.stateLabel.text = "Current state: \(mySwitch.on)"
}
func toggleSwitch() {
// Each time we toogle switch, save switch state
defaults.setBool(mySwitch.on, forKey: savedKey)
defaults.synchronize()
// Update label
self.stateLabel.text = "Current state: \(mySwitch.on)"
}
func clearSettings() {
// Remove saved switch state
defaults.removeObjectForKey(savedKey)
}
}

Related

Why isn't my UIBarButtonItem calling my custom function?

I have a UIViewController with a UIBarButtonItem configured to call my custom function called func settingsTapped(sender: AnyObject?) in which I've put performSegueWithIdentifier. I've determined that this function isn't being called even though the button DOES work and the segue somehow still works because it goes to the correct view controller.
First VC:
class CalculatorViewController: UIViewController {
private let timeInterval = 0.016 //how frequently we change the displayed number
private let animationLength = 0.3
private var timer: NSTimer?
private var counter: Int = 0
private var max: Double = 0.0
var startingPreferredUnits: String?
#IBOutlet weak var weightLifted: UITextField!
#IBOutlet weak var repetitions: UITextField!
#IBOutlet weak var oneRepMax: UILabel!
#IBOutlet weak var percentages: UITextView!
#IBOutlet weak var units: UILabel!
override func viewDidLoad() {
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))
self.navigationItem.leftBarButtonItem = settingsButton
super.viewDidLoad()
}
func settingsTapped(sender: AnyObject?) {
startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging
self.performSegueWithIdentifier("segueToSettings", sender: self)
}
}
In the storyboard, I placed a Bar Button Item in the nav bar:
I created a Show (e.g. Push) segue between the Settings bar button item and the Settings view controller and I've given this segue an identifier of 'segueToSettings`. When I touch the Settings button, it does present the Settings view controller, but it doesn't print my caveman debugging line to the console.
I also tried creating the segue between the CalculatorViewController itself and the SettingsViewController (which I think may even be a better way) but when I have it set up that way, nothing happens at all when I touch the Settings button.
I've tried everything I could find on SO but nothing has worked. I hope I don't earn a Stupid Question badge on this one.
UPDATE 1:
I'm still struggling with this one. Here's something else I've learned and tried that didn't work. Clicking the Settings button works by performing the segue from the button to the Settings page which I created in the Storyboard. As one would expect, it will call override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) before doing this. So it's completely ignoring the action: #selector(settingsTapped(_:)) part. Also, I can change the identifier of the segue in the storyboard and it makes no difference at all. It still works. I can even delete the identifier and it works.
I also tried adding another button (this time a barButtonSystemItem like so:
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))
let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveThisLift(_:)))
self.navigationItem.leftBarButtonItem = settingsButton
self.navigationItem.rightBarButtonItem = saveButton
}
func saveThisLift(sender: UIBarButtonItem) {
print("I'm in saveThisLift") // never prints
let weight = weightLifted.text
let reps = repetitions.text
let maxAmount = oneRepMax.text
let unitsText = units.description
coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText)
}
func settingsTapped(sender: AnyObject?) {
startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging
self.performSegueWithIdentifier("segueToSettings", sender: self)
}
Like the Settings button, touching it does nothing except make it flash. I should point out that I added the bar button items via the storyboard and I think that's unnecessary since I'm trying to add them programmatically. However, without them, neither of the buttons appears.
After a lot more research and more trial and error, I've figured it out. I learned a number of things that I'll leave here for anyone who has this problem in the future.
At a high-level, trying to do some of this in the storyboard and some in code made it easy to get confused. The key things for me were:
I wasn't dealing with a UINavigationController with its out-of-the-box root view controller, I was dealing with a UIViewController. With the UINavigationController, you don't have to do as much. But with a UIViewController, I had to add a UINavigationBar and to that, I had to add a single UINavigationItem. I did this by dragging them from the Object Library to my storyboard.
Don't try to put a UILabel in the UINavigationBar to do what I was trying to do, which was have them call custom functions. Trust me, it doesn't work, at least not in my case.
After figuring out I needed to add a UINavigationItem, the next ah-ha! moment for me was the fact that I could put multiple UIBarButtonItems in it (note: my example in my question shows just one to keep it simple but I'm actually adding three items)
The magic piece of the puzzle was connecting my code to the storyboard. I simply Ctrl-clicked from my UINavigationItem to my view controller and created an #IBOutlet called #IBOutlet weak var navItem: UINavigationItem!
My code that creates the buttons and adds them to the view uses this outlet like so (simplified):
_
class CalculatorViewController: UIViewController {
#IBOutlet weak var navItem: UINavigationItem!
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(self.segueToSettings(_:)))
let viewLogButton = UIBarButtonItem(title: "Log", style: .Plain, target: self, action: #selector(self.segueToLog(_:)))
let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveLift(_:)))
self.navItem.leftBarButtonItem = settingsButton
self.navItem.rightBarButtonItems = [saveButton, viewLogButton]
}
func saveLift(sender: AnyObject) {
let weight = weightLifted.text
let reps = repetitions.text
let maxAmount = oneRepMax.text
let unitsText = units.text
coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText!)
performSegueWithIdentifier("segueToLog", sender: self)
}
}
Lastly, when you create that #IBOutlet, don't name it navigationItem: UINavigationItem because that will make Xcode very unhappy:
I burned a lot of hours on this one. I hope this information helps somebody avoid that in the future.

Swift - UIToolbar item selected state

I have this toolbar in my navigation controller. Now what I am trying to do is when the user selects an item (UIBarButtonItem) in my toolbar, have that item highlighted with a background colour until either the user deselects the item or selects another item. How would I do this?
Here are my selector methods for each item of the toolbar, I connected them via storyboard:
#IBAction func addText(sender: AnyObject) {
annotationSelected = 3
}
#IBAction func drawCircle(sender: AnyObject) {
annotationSelected = 1
}
#IBAction func drawRectangle(sender: AnyObject) {
annotationSelected = 2
}
#IBAction func drawStamp(sender: AnyObject) {
annotationSelected = 4
}
This is all I have done. Here is a screenshot of my toolbar:
Here is what I got:
#IBOutlet var textToolButton: UIBarButtonItem!
#IBOutlet var circleToolButton: UIBarButtonItem!
#IBOutlet var rectangleToolButton: UIBarButtonItem!
#IBOutlet var stampToolButton: UIBarButtonItem!
then
textToolButton.target = self
textToolButton.style = .Done
textToolButton.action = #selector(ViewController.barButtonPressed)
let selectedBackgroundColor = UIImage(color: .redColor())
textToolButton.setBackgroundImage(selectedBackgroundColor, forState: UIControlState.Highlighted, style: .Done, barMetrics: UIBarMetrics.Default)
and then the method
func barButtonPressed(sender: UIBarButtonItem) {
print(sender)
annotationSelected = sender.tag
}
background is still not changing color
I find a same question. May be can help you.
custom-pressed-uibarbuttonitem-backgrounds
I find a easy method to do. You can dray a button to the toolBar,and you will see like this.
And you should change the button's type and Image.
storyboard screenshot
then you should link the button to your viewController.
#IBOutlet weak var textToolButton: UIButton!
and you can do.
let selectedBackgroundColor = UIImage(color: .redColor())
textToolButton.setBackgroundImage(selectedBackgroundColor, forState: .Highlighted)
May be I can help you.
The cleanest way you could do it, is create an overall function where you pass in the button that's been selected. Something like this:
var allButtons = [button1, button2, button3, button4]
func resetTabBar (buttonSelected:UIButton) {
for button in allButtons {
if button == buttonSelected {
button.backgroundColor = "Black"
}
else {
button.backgroundColor = "Blue"
}
}
}
And then in your functions you've created, just pass in the sender like so:
#IBAction func addText(sender: AnyObject) {
resetTabBar(sender)
}
Note: This is assuming you have outlets for all of your buttons. If you don't, add them.

Memory usage increases with every segue to modal vc

I have two view controllers, one is the timeline the second one is for the creation. In that second view controller I have a sub view. This sub view is an SKView. Now every time I segue to it, it increases the memory usage by 2mb (on a real device), but the memory usage stays the same when I unwind it.
So it is like this: I start with a usage of 12mb, then it gets 14-15mb. After the unwind it stays around 14-15mb. After the second segue to it, it becomes 17mb... and so on.
This is the code used in the timeline controller:
#IBAction func createButtonAct(sender: AnyObject) {
self.performSegueWithIdentifier("create", sender: self)
}
#IBAction func unwindFromCreation(segue: UIStoryboardSegue) {
}
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "create"{
let vc = segue.destinationViewController as! CreateViewController
if vc.currCountry == nil || vc.currCountry != self.currCountry{
vc.currCountry = self.currCountry
}
}
}
And this is the code in the create View Controller:
class CreateViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var bubbleView: SKView!
#IBOutlet var arrow: UIButton!
var ref: Firebase!
let categories = CatsAndColors.categories
#IBOutlet var doneButton: UIButton!
#IBOutlet var titleField: UITextField!
#IBOutlet var descriptionView: KMPlaceholderTextView!
var choosedCat: String!
var selectedCats: NSMutableArray!
var currCountry:String!
var tap: UITapGestureRecognizer!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
UIApplication.sharedApplication().statusBarStyle = .Default
UIView.animateWithDuration(0.5, animations: { () -> Void in
self.arrow.transform = CGAffineTransformMakeRotation(3.14159)
})
titleField.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
titleField.addTarget(self, action: "textFieldDidBegin:", forControlEvents: UIControlEvents.EditingDidBegin)
titleField.addTarget(self, action: "textFieldDidEnd:", forControlEvents: UIControlEvents.EditingDidEnd)
// the targets get removed in viewWillDisappear
selectedCats = NSMutableArray()
}
override func viewDidLoad() {
super.viewDidLoad()
ref = Firebase(url: "https://blabber2.firebaseio.com")
self.doneButton.enabled = false
doneButton.setBackgroundImage(UIImage(named: "Done button inactive"), forState: .Disabled)
doneButton.setTitleColor(UIColor(netHex: 0xF6F6F6), forState: .Disabled)
doneButton.setTitleColor(UIColor.whiteColor(), forState: .Normal)
self.setupBubbles()
self.descriptionView.delegate = self
}
func setupBubbles(){
let floatingCollectionScene = ChooseBubblesScene(size: bubbleView.bounds.size)
// floatingCollectionScene.scaleMode = .AspectFit
/*let statusBarHeight = CGRectGetHeight(UIApplication.sharedApplication().statusBarFrame)
let titleLabelHeight = CGRectGetHeight(self.tibleLabel.frame)*/
bubbleView.presentScene(floatingCollectionScene)
for (category, color) in categories {
let node = ChooseBubbleNode.instantiate()
node!.vc = self
node!.fillColor = SKColor(netHex: color)
node!.strokeColor = SKColor(netHex: color)
node!.labelNode.text = category
floatingCollectionScene.addChild(node!)
}
}
...
And the catsAndColors struct looks like this:
struct CatsAndColors{
static var categories = ["Crime":0x5F5068, "Travel":0xFBCB43, "Religion":0xE55555, "Tech":0xAF3151, "Economy":0x955BA5, "Games":0xE76851, "Climate":0x6ED79A, "Books":0xE54242, "History":0x287572, "Clothes":0x515151, "Sports":0x4AB3A7, "Food":0xD87171, "Politics":0x5FA6D6, "Music":0xDD2E63, "Tv-shows":0x77A7FB]
}
Maybe you have created some sort of retain cycle between your view controllers.
If both view controllers hold a reference to each other, then try declaring one of the references as weak.
For more information on the topic read Resolving Strong Reference Cycles Between Class Instances.
I solved the problem, it was strong reference in the sknode file.
Thank you for your answers.

How to stop UIButton from highlighting/flash each time its title is reset?

I'm using a UIButton as a label to display some number in its title. Every time the function is called to reset the title of this button, it flashes. It does not affect the functionality but hurts user experience. I'm wondering if there is a way to stop UIButton from this highlighting behavior?
Thanks in advance!
Edit: Following are the code where I'm simply calling a delegate method updateDigits to refresh the button's title.
class ViewController: UIViewController, UIPopoverPresentationControllerDelegate, DigitsEntryViewDelegate {
#IBOutlet weak var tipField1: UIButton!
#IBOutlet weak var tipField2: UIButton!
var buttonsArray = [UIButton]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.tipField1.tag = 0
self.tipField2.tag = 1
self.tipField1.addTarget(self, action: "inputFieldClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.tipField2.addTarget(self, action: "inputFieldClicked:", forControlEvents: UIControlEvents.TouchUpInside)
self.buttonsArray.append(self.tipField1)
self.buttonsArray.append(self.tipField2)
}
func inputFieldClicked(sender: UIButton) {
let anchor: UIView = sender
let viewControllerForPopover = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("DigitEnter") as! DigitsEntryViewController
// set popover API values
viewControllerForPopover.currentDigitsString = sender.currentTitle
viewControllerForPopover.tagNumber = sender.tag
viewControllerForPopover.delegate = self
viewControllerForPopover.preferredContentSize = CGSizeMake(240, 320)
let popover = UIPopoverController(contentViewController:viewControllerForPopover)
popover.presentPopoverFromRect(anchor.frame, inView: anchor.superview!, permittedArrowDirections: UIPopoverArrowDirection.Down, animated: false)
}
func updateDigits(returnedDigits: String, tagNumber: Int) {
self.buttonsArray[tagNumber].setTitle(returnedDigits, forState: UIControlState.Normal)
}
}
Try setting the button type to Custom, not System.
I could be mistaken, but the flashing looks like it may be caused by a data delivery delay; you are attempting to change the title while the new title is still being retrieved, hence the flash.
I would:
Retrieve the data and store it in a temporary, but purpose created string.
After the delivery of data is made, call a method to change the text of the button to that of the stored string.
This should fix your issue:
[UIView performWithoutAnimation:^{
[self.myButton setTitle:text forState:UIControlStateNormal];
[self.myButton layoutIfNeeded];
}];

How do I set title for UIBarButtonItem?

I'm a beginner iPhone developer.
How can I programmatically set the title for the UIBarButtonItem?
My code is the following:
self.navigationItem.rightBarButtonItems =
UIBarButtonItem(
barButtonSystemItem: .Cancel, target: self,
action: "barButtonItemClicked:")
#IBAction func barButtonItemClicked(sender: UIBarButtonItem) {
//print something
}
Use different initialiser that allows you to specify the title:
UIBarButtonItem(title: "title", style: .Plain, target: self, action: "barButtonItemClicked:")
Swift 3.1 Update
UIBarButtonItem(title: "title", style: .plain, target: self, action: #selector(barButtonItemClicked))
Swift 2 answer
You could just add the following to viewDidLoad:
// Some text
self.barButtonItemClicked.title = "Right Button!"
OR
// A Unicode Gear Symbol
// See: http://graphemica.com/%E2%9A%99
self.barButtonItemClicked.title = "\u{2699}"
The ViewController.Swift code below would set the name barButtonItemClicked you used. I used a UIBarButtonItem to show how to set the title. But it is trivial to adapt it for a function.
import UIKit
class ViewController: UIViewController {
// Please make sure the line below is properly connected to your Storyboard!
#IBOutlet weak var barButtonItemClicked: UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Set the text of the NavBarButtonItem Title
self.barButtonItemClicked.title = "Right Button!"
// Gear Icon
// self.barButtonItemClicked.title = "\u{2699}"
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Swift 4:
#IBOutlet var saveButton: UIBarButtonItem!
saveButton.title = "Saved"
In my case, I wanted to toggle between the Edit|Done.
However, I couldn’t use the leftBarButtonItem because I already had another UIBarButtonItem.
What I did is the following:
1- Create #IBOutlet weak var edit: UIBarButtonItem!
2- Then a variable to hold the state: var isEditingMode = false
3- Now in the viewDidLoad:
override func viewDidLoad() {
…
self.edit.action = #selector(self.toogleEditor(_:))
self.edit.title = "Edit"
self.setEditing(isEditingMode, animated: true)
…
}
I initialize the edit.action Selector to my custom function toogleEditor. I want to be able to change the title whenever an action occur.
4- Create an IBAction:
#IBAction func toogleEditor(sender: AnyObject) {
if isEditingMode
{
isEditingMode = false
self.edit.title = "Edit"
}
else
{
isEditingMode = true
self.edit.title = "Done"
}
self.setEditing(isEditingMode, animated: true)
}
This function is triggered each time the user click the UIBarItemButton.
The only thing to do is use the setEditing(…) to change the behaviour of the UITableViewController.
If anyone wondering how to do it from the storyboard:
Try to edit the "Title" attribute in "Attributes Inspector" as shown below:
from the docs
init(title title: String?,
style style: UIBarButtonItemStyle,
target target: AnyObject?,
action action: Selector)

Resources