In a single VC app,
a info label is used to display textually the number of clicks of a button,
idea is to toggle the text on even/ uneven number of clicks. I did this ok,
but the problem came when I tried to link couple ( 9 ) UIButtons to the single IBAction where count and label updates happen. Here the label update lags one step behind the real count ( verified by print(counter) on Console).
May someone help why this happens?
I can do it of course with separate IBAction for each UIButton,
but one action and buttons tags works fine, so it would be nice to keep the code less by single IBAction.
import UIKit
class ViewController: UIViewController {
var player:Player = .cross
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.label.text = "Player \(player.rawValue) is on the move"
}
#IBAction func Move(_ sender: UIButton) {
if player == .cross {
player = .circle
} else {
player = .cross
}
print(sender.tag)
print("Player \(player.rawValue) is on the move")
self.label.text = "Player \(player.rawValue) is on the move"
}
#IBAction func button(_ sender: UIButton) {
if player == .cross {
player = .circle
} else {
player = .cross
}
print(sender.tag)
print("Player \(player.rawValue) is on the move")
self.label.text = "Player \(player.rawValue) is on the move"
}
}
// the move function is the common IBAction, which lags the label update
// the func button is action for a separate button, doing label updates correctly
// debug progress: similar code worked fine without lag on other desktop,
moreover working code there clean, lags when i run it on my desktop.
I have doubts it might be simulator issue.
For the record, my xcode did it's latest amend few days ago.
Related
I am trying to use two buttons to toggle between an animated sliding view. When the UIView loads, I want button1 to be UIColor.darkGrey and button2 to be UIColor.lightGrey. Then, when I press button2 I want button2 to become UIColor.darkGrey and button1 to become UIColor.lightGrey. If I press button1, I want button1 to be UIColor.darkGrey and button2 to be UIColor.lightGrey.
It seems simple; Using the storyboard, I connected a UIButton for button1 and button2 as an outlet. Then I connected each as actions. In each of the actions, I included the following code:
#IBAction func button1Action(_ sender: UIButton) {
button2.titleLabel?.textColor = UIColor.lightGray
button1.titleLabel?.textColor = UIColor.darkGray
UIView.animate(withDuration: 1){
self.side1.constant = 0
self.side2.constant = 0
self.sideA.constant = 400
self.sideB.constant = -400
self.view.layoutIfNeeded()
}
}
#IBAction func button2Action(_ sender: UIButton) {
button1.titleLabel?.textColor = UIColor.lightGray
button2.titleLabel?.textColor = UIColor.darkGray
view.layoutIfNeeded()
UIView.animate(withDuration: 1){
self.side1.constant = -400
self.side2.constant = 400
self.sideA.constant = 0
self.sideB.constant = 0
self.view.layoutIfNeeded()
}
}
When I press button1, everything works as expected; However, whenever I press button2 both buttons are UIColor.lightGrey. Am I missing something obvious?
You get some methods "for free" with buttons to manage their state. One of them is isSelected. Another is the tag property, so you can figure out which button is which. Since you've only got two buttons, you can get away with just using isSelected to figure out which is which. You can also use computed vars to make your life easier. With those things in mind, here's one approach you could utilize to managing the buttons' states:
Declare a buttons computed var, like so
#IBOutlet var firstButton: UIButton!
#IBOutlet var secondButton: UIButton!
// computed var to access your buttons
private var buttons: [UIButton] {
return [firstButton, secondButton]
}
Set up your buttons in viewDidLoad.
override func viewDidLoad() {
super.viewDidLoad()
// one setup to configure each button for selected or not selected
buttons.forEach { button in
button.setTitleColor(.darkGray,
for: .selected)
button.setTitleColor(.lightGray,
for: .normal)
}
firstButton.isSelected = true
}
func setTitleColor(_ color: UIColor?, for state: UIControl.State) will remain in effect for the lifetime of the button, so you don't need to fiddle with it after the initial declaration (unless you want to change the button's behavior later).
One #IBAction for both buttons and utilize the tag property to figure out which one is which. Since you've only got two buttons, I'm just using isSelected. Here's what your single #IBAction would look like:
#IBAction func buttonAction(_ sender: UIButton) {
UIView.animate(withDuration: 1.0) {
// flip buttons
self.buttons.forEach { button in
button.isSelected.toggle()
}
if self.firstButton.isSelected {
// tweak your constraints for firstButton.isSelected == true
// tweak your constraints for secondButton.isSelected == false
} else {
// tweak your constraints for firstButton.isSelected == false
// tweak your constraints for secondButton.isSelected == true
}
}
}
Based on your current implementation, you'll need to right click on the UIButtons on storyboard and nuke the existing IBAction connections and reconnect both buttons to the method above.
I am new to iOS development, I have created android app where app gets sets of images (sometimes over 100 images) from urls and loads into imageView with zoomEnabled Inside ViewPager.
Now I want to create same app for iOS, I have found this lib ImageSliderShow. Problem with this is it shows fullscreen ONLY when user did tap on selected image. I have been struggling to presentFullScreenController on viewDidLoad with no lock.
I only want image to be shown in fullScreen, example:
Select Category A From CategoryVC -> Loads ImageSlideVC, gets set of images from server, show in FullScreenView.
How can i achieve this? Adding this:
slideshow.presentFullScreenController(from: self)
on viewDidLoad didn't work:
#IBOutlet var slideshow: ImageSlideshow!
let kingfisherSource = [KingfisherSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!,
KingfisherSource(urlString: "https://images.unsplash.com/photo-1447746249824-4be4e1b76d66?w=1080")!,
KingfisherSource(urlString: "https://images.unsplash.com/photo-1463595373836-6e0b0a8ee322?w=1080")!]
override func viewDidLoad() {
super.viewDidLoad()
slideshow.setImageInputs(kingfisherSource)
slideshow.presentFullScreenController(from: self)
}
I dont get any error, output is same as before (shows slideshow in smallview with no zoom)
Please help
EDIT
by doing in viewDidAppear, after split second view is loaded its loads into fullscreen. First its smallview then shows in fullscreen. I think i have to do something inside setImageInputs
this is what its looks like :
open func setImageInputs(_ inputs: [InputSource]) {
self.images = inputs
self.pageControl.numberOfPages = inputs.count
// in circular mode we add dummy first and last image to enable smooth scrolling
if circular && images.count > 1 {
var scImages = [InputSource]()
if let last = images.last {
scImages.append(last)
}
scImages += images
if let first = images.first {
scImages.append(first)
}
self.scrollViewImages = scImages
} else {
self.scrollViewImages = images
}
reloadScrollView()
layoutScrollView()
layoutPageControl()
setTimerIfNeeded()
}
try to do it in viewDidAppear
#IBOutlet var slideshow: ImageSlideshow!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
slideshow.setImageInputs(kingfisherSource)
slideshow.presentFullScreenController(from: self)
}
Presenting viewController in ViewDidLoad is a bad practice.
I'm new to Swift and I assume this is a fundamental question to programming for iOS.
I have three buttons in my storyboard and I want to customize how those buttons look if pressed once, twice and three times.
I also have three themes (pink, blue and orange). What I thought of doing is to create three new classes called pink,blue and orange.swift
I don't want to create them programmatically, only style them programmatically.
What I lack to understand is how do I call the function (Example: "ButtonIsPressed") from my pink.swift class into my #IBAction and #IBOutlet in the main view controller that is also object oriented (ie. I don't want to create a function for every button)?
I can't really find a decent and up-to-date Swift 3 Tutorial for this, any help or advice on this topic will be greatly appreciated.
Why can it not be as simple as?:
#IBAction func buttonPressed(_ sender: UIButton!) {
self.backgroundColor = myPinkCGolor
}
I think shallowThought's answer will work for changing backgroundColor based on button state of a specifically named IBOutlet.
I have three buttons in my storyboard and I want to customize how those buttons look if pressed once, twice and three times.
If you want to maintain "state", as in have a "counter" for how many times a button's been clicked or tapped, you can use the "tag" property of the button. Set it to zero, and in your IBAction functions increment it. (Like shallowThought said, use .touchUpInside and .touchDown for the events.)
Also, you have one minor - but important! - thing wrong in your code Brewski:
#IBAction func buttonPressed(_ sender: UIButton!) {
self.backgroundColor = myPinkCGolor
}
Should be:
#IBAction func buttonPressed(_ sender: UIButton!) {
sender.backgroundColor = myPinkCGolor
}
So combining everything - up vote to shallowThought (also, changing his AnyObject to UIButton and making it Swift 3.x syntax on the UIColors - and would end up with this. Note that there is no need for an IBOutlet, and you can wire everything up in IB without subclassing:
// .touchUpInside event
// can be adapted to show different color if you want, but is coded to always show white color
#IBAction func buttonClicked(sender: UIButton) {
sender.backgroundColor = UIColor.whiteColor()
}
// .touchDown event
// will show a different color based on tap counter
#IBAction func buttonReleased(sender: UIButton) {
switch sender.tag {
case 1:
sender.backgroundColor = UIColor.blue
case 2:
sender.backgroundColor = UIColor.red
case 3:
sender.backgroundColor = UIColor.green
default:
sender.backgroundColor = UIColor.yellow
}
sender.tag += 1
}
There is no methode to set the backgroundColor for a certain state, like there is for other UIButton properties, so you have to listen to the buttons actions:
class ViewController: UIViewController {
#IBOutlet weak var button: UIButton!
#IBAction func buttonClicked(sender: AnyObject) { //Touch Up Inside action
button.backgroundColor = UIColor.whiteColor()
}
#IBAction func buttonReleased(sender: AnyObject) { //Touch Down action
button.backgroundColor = UIColor.blueColor()
}
...
}
or set a unicolor image withimage:UIImage, forState:.selected.
i want to make 2 action for a button like that.
selected and deselected action for 1 button.
#IBAction func btntouch(sender: UIButton) {
if firsttouch
{
print bla bla
change button to selected style. maybe background color.
}
else
{
}
}
how can i do that?
In case you need to split two button statuses - like ON and OFF, try this:
var buttonSwitched : Bool = false
#IBAction func btntouch(sender: UIButton) {
//this line toggle your button status variable
//if true, it goes to false, and vice versa
self.buttonSwitched = !self.buttonSwitched
if self.buttonSwitched
{
//your UI styling
}
else
{
//your opposite UI styling
}
}
Create 2 IBActions:
#IBAction func touchDown(_ sender: AnyObject) {
print("down")
}
#IBAction func touchUp(_ sender: AnyObject) {
print("up")
}
When connecting the first one, make sure the event is set to touchDown. For the second one, make sure it is set to touchUpInside
Yes, you can. Depending on your requirements, you could store the current state of the button in the view controller or in the model.
If the visual change caused by the first touch needs to be persisted across opening and closing of your view controller, store the value indicating the change in the model; if you need to reset the visuals when the view controller shows, store the value in the view controller itself:
var firstTouch = true
#IBAction func btntouch(sender: UIButton) {
if firstTouch {
firstTouch = false
...
} else {
...
}
}
I edited some code based on what others have pointed out, but I keep getting the error stated above, saying I sent an "unrecognised selector". The selector for my timer, originally the error, has been amended, but Xcode is still complaining.
Here is my code:
import UIKit
class ViewController: UIViewController {
#IBOutlet var instructionsNew: UILabel!
#IBOutlet var lockStatusNew: UIImageView!
#IBOutlet var timerText: UILabel!
#IBAction func hackLockButton(sender: AnyObject){
var counter = 0
let timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: "updateCounter", userInfo: nil, repeats: true)
func updateCounter() {
timerText.text = String(counter++)
}
while(timerText.text == "1") {instructionsNew.text = "loading"}
while(timerText.text == "2"){instructionsNew.text = "loading."}
while(timerText.text == "3") {instructionsNew.text = "loading.."}
while(timerText.text == "4"){instructionsNew.text = "loading..."}
while(timerText.text == "5") {instructionsNew.text = "hack successful!"
lockStatusNew.image = UIImage(named: "unlocked.png")
timer.invalidate()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Please help me spot the problem. Thanks!
The issue is, you added the updateCounter inside the hackLockButton function.
You should place the method outside that function and it will work.
#IBAction func hackLockButton(sender: AnyObject)
{
// Code here
}
func updateCounter()
{
timerText.text = String(counter++)
}
Suggestion:
You don't want to write while(timerText.text == "1") {instructionsNew.text = "loading"} for showing that label. It can cause an infinite loop and hang your UI. Instead use a switch case like:
switch(counter)
{
case 1: instructionsNew.text = "loading"
// Write other cases too
}
I think you really, really need to get your head around how a timer works.
Your application has a run loop. When the user does anything, the run loop will call the appropriate code in your program, runs the code, and finish when that code is run. For example when you tap on a button, the run loop will call your button callback function, wait for it to finish, and then it can wait for the next thing to happen.
A scheduled timer inserts calls into that run loop. So every second the run loop calls updateCounter. updateCounter should do some stuff, and then return. It's not supposed to wait in a while loop at all. The while () inside it is badly, badly wrong.
You also do some other things upside down. You use your timerText label to control things. That's wrong. The label should display things. The updateCounter can update the counter, but then all other actions should depend on the value of the counter, not on the value of a user interface label! Imagine your boss tells you to display not 1, 2, 3, 4, 5 but one, two, three, four, five. You obviously change what goes into the label. But with your code you have to change code everywhere that reads the text of the label. Now imagine you don't want one, two, three, but the right text in the user's language...