I'm looking for a way to interrupt viewWillDisappear when the back button is pressed (on UINavigationController), AND a certain data condition occurs, but I cannot find any way to logically keep the user on the view until my conditions have been met.
import UIKit
class DetailViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var serialNumberField: UITextField!
#IBOutlet weak var valueField: UITextField!
#IBOutlet weak var dateLabel: UILabel!
// adds item.name to top of detail view
var item: Item! {
didSet {
navigationItem.title = item.name
}
}
// ************************************
// MARK: Number / Date formatters
// ************************************
let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
// ************************************
// MARK: viewWillAppear
// ************************************
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
nameField.text = item.name
serialNumberField.text = item.serialNumber
valueField.text = numberFormatter.string(from: NSNumber(value: item.valueInDollars))
dateLabel.text = dateFormatter.string(from: item.dateCreated)
}
// ************************************
// MARK: viewWillDisappear
// ************************************
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// clear first responder...
// this creates smoother transition if the
// keyboard is visible when the back button is pressed.
view.endEditing(true)
// save changes to item
item.name = nameField.text ?? ""
item.serialNumber = serialNumberField.text
if let valueText = valueField.text, let value = numberFormatter.number(from: valueText) {
// item.valueInDollars = value.intValue
// Rounding value of item before passing it back.
item.valueInDollars = Int(round(Float(truncating: value)))
} else {
item.valueInDollars = 0
}
}
// *************************************
// MARK: Resign keyboard with return key
// *************************************
// resign keyboard when return pressed from within textFields
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// ************************************
// MARK: backGroundTapped TapGesture
// ************************************
#IBAction func backgroundTapped(_ sender: UITapGestureRecognizer) {
view.endEditing(true)
}
}
Related
I want to change borderColor from lightGray to black when textField is firstResponder. And if the textField resigns again, I want to change it to gray. TextField's textColor changes color automatically without tableView's reload, but borderColor doesn't. Is there a way to change the color by observing the values without reloading the tableView?
class ChangePasswordViewController: UITableViewController {
#IBOutlet weak var newPasswordTextField: RoundTextField!
#IBOutlet weak var checkingNewPasswordTextField: RoundTextField!
#IBOutlet weak var explainLabel: UILabel!
#IBOutlet weak var changePwButton: FillRoundButton!
let viewModel = ChangePasswordViewModel()
override func viewDidLoad() {
super.viewDidLoad()
hideKeyboardWhenTappedAround()
newPasswordTextField.tag = 0
checkingNewPasswordTextField.tag = 1
configureNotificationObservers()
}
func configureNotificationObservers() {
newPasswordTextField.addTarget(self, action: #selector(textDidChange(sender:)), for: .editingChanged)
checkingNewPasswordTextField.addTarget(self, action: #selector(textDidChange(sender:)), for: .editingChanged)
}
// MARK: - Actions
#objc func textDidChange(sender: RoundTextField) {
if sender == newPasswordTextField {
viewModel.pwValue = sender.text
} else {
viewModel.confirmPwValue = sender.text
}
updateForm()
}
}
extension ChangePasswordViewController: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.tag == 0 {
viewModel.pwValue = textField.text ?? ""
} else {
viewModel.confirmPwValue = textField.text ?? ""
}
}
}
extension ChangePasswordViewController: FormViewModel {
func updateForm() {
changePwButton.isEnabled = viewModel.pwFormIsValid
newPasswordTextField.layer.borderColor = UIColor.yellow.cgColor
newPasswordTextField.textColor = .yellow
}
}
enter image description here
I want to implement a function that activates the button when the text field and date picker are set.
The value of the date picker goes to the label, and I checked the button by changing the label value.
However, among other functions that I have implemented, if the navigation is popped using a singleton object and the value is saved when it comes back, if the button is checked with a label change, the button is not activated because it is duplicated with the singleton, or even if only the phone number is set. It happens that the button is activated.
So, rather than changing the label, is there a way to catch it when the date picker itself changes the value?
import UIKit
class ThirdViewController: UIViewController {
lazy var dateFormatter: DateFormatter = {
let date = DateFormatter()
date.dateStyle = .medium
date.timeStyle = .none
return date
}()
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var numberTextField: UITextField!
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var signUpButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
numberTextField.addTarget(self, action: #selector(editingChanged(_:)), for: .editingChanged)
numberTextField.text = UserInformation.userInformationSingleton.userNumber
dateLabel.text = UserInformation.userInformationSingleton.dateLabel
}
#objc func editingChanged(_ textField: UITextField) {
if textField.text?.count == 1 {
if textField.text?.first == " " {
textField.text = ""
return
}
}
toggleButton()
}
func toggleButton() {
guard
let textField = numberTextField.text, !textField.isEmpty,
dateLabel.text != ""
else {
signUpButton.isEnabled = false
return
}
signUpButton.isEnabled = true
}
#IBAction func datePickerValueChanged(_ sender: UIDatePicker) {
dateLabel.text = dateFormatter.string(from: sender.date)
toggleButton()
}
#IBAction func cancelButton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
UserInformation.userInformationSingleton.userID = nil
UserInformation.userInformationSingleton.userNumber = nil
UserInformation.userInformationSingleton.dateLabel = nil
}
#IBAction func tappedPreviousButton(_ sender: UIButton) {
self.navigationController?.popViewController(animated: true)
}
#IBAction func tappedSignUpButton(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
}
https://i.stack.imgur.com/a1GVc.png
thanks for reading!!
solved the problem!
func toggleButton() {
guard
let textField = numberTextField.text, !textField.isEmpty,
let label = dateLabel.text, !label.isEmpty
else {
signUpButton.isEnabled = false
return
}
signUpButton.isEnabled = true
}
I just begin to learn iOS dev, here I want to show a datePicker and get date from the user, but I don't know how to show a DataPicker from the bottom of screen. After searching, I found that: dataPicker can be activated by a textField simply by "myTextField.inputView = myDatepicker". I did that, but I faced questions:
After activating the datePicker, the user is able to cut, paste the date shown in the textField. Is there some way to disable the selection of the content in the textField and also the editing menu?
Instead of using a textField, if I want to use a UIButton/UILabel/cell to activate the dataPicker, How can I do that?(I mean how to show the DatePicker from the bottom. I found UIButton/UILabel/cell have no method .inputView like textField does)
It may simple, but really confused me, this is the first app I am trying to do. Any help is very much appreciated, especially in detail, in Swift. Thank you very much.
Way 1
Use a button to show the date and a button to show/ hide the datepicker embedded in a view.
class ViewController: UIViewController {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var datePicker: UIDatePicker!
#IBOutlet weak var chooseDate: UIButton!
#IBAction func toggleDatePicker(_ sender: Any) {
datePicker.isHidden = !datePicker.isHidden
}
override func viewDidLoad() {
dateLabel.text = stringFrom(date: Date())
datePicker.isHidden = true
}
}
extension ViewController: UIPickerViewDelegate {
#IBAction func valueChanged() {
dateLabel.text = stringFrom(date: datePicker.date)
}
private func stringFrom(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
return dateFormatter.string(from: datePicker.date)
}
}
Way 2
Use a button to show the date and a button to show/ hide the datepicker from the bottom.
Just use the following protocol that I wrote:
protocol DatePickable: class {
var textField: UITextField { get }
var datePicker: UIDatePicker { get }
var isDatePickerVisible: Bool { get set }
}
extension DatePickable {
func prepareDatePickerFor(view: UIView, onChangeAction: Selector) {
datePicker.datePickerMode = UIDatePickerMode.date
datePicker.addTarget(self, action: onChangeAction, for: .valueChanged)
textField.inputView = datePicker
view.addSubview(textField)
}
func toggleDatePicker() {
isDatePickerVisible ? hideDatePicker() : showDatePicker()
isDatePickerVisible = !isDatePickerVisible
}
private func showDatePicker() {
textField.becomeFirstResponder()
}
private func hideDatePicker() {
textField.resignFirstResponder()
}
}
How to use the protocol in you class:
class ViewController: UIViewController, DatePickable {
// Required by Datepickable
var isDatePickerVisible = false
var textField: UITextField = UITextField()
var datePicker: UIDatePicker = UIDatePicker()
// IBOutlets
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var chooseDate: UIButton!
override func viewDidLoad() {
prepareDatePickerFor(view: view,
onChangeAction: #selector(ViewController.onDidChangeDate(_:)))
dateLabel.text = stringFrom(date: Date())
}
#IBAction func toggleDatePickerVisibility(_ sender: Any) {
toggleDatePicker()
}
#IBAction func onDidChangeDate(_ sender: Any) {
dateLabel.text = stringFrom(date: datePicker.date)
}
private func stringFrom(date: Date) -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd-MM-yyyy"
return dateFormatter.string(from: datePicker.date)
}
}
So you have to add textfield delegate and then recognise the right textfield:
let datePicker = UIDatePicker()
func viewDidLoad() {
super.viewDidLoad()
txtDatePicker.delegate = self
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == txtDatePicker {
showDatePicker()
}
return true
}
Then inside showDatePicker you will tell your picker what mode should it be as follows:
func showDatePicker() {
//Formate Date
datePicker.datePickerMode = .date
// if you need a toolbar, here is a good place to define it
// add datepicker to textField
txtDatePicker.inputView = datePicker
}
Then in delegate function or toolbar selection you do as follows:
func donedatePicker() {
//For date formate
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yyyy"
txtDatePicker.text = formatter.string(from: datePicker.date)
self.view.endEditing(true)
}
I'm trying to update two buttons with datepicker. Button startDate et endDate. I don't how can I update these butttons with differents datepicker.
Maybe with handler ?
user story : the user click on startDat datepicker is displaying, user choose any date or time, then click on endDate and same action.
EDIT:
class ViewController: UIViewController {
#IBOutlet weak var dateTextField: UITextField!
#IBOutlet weak var dateTextLabel: UILabel!
var whoTriggeredPickerView: UIButton?
#IBOutlet weak var startDateTextButton : UIButton!
#IBOutlet weak var endDateTextButton : UIButton!
#IBOutlet weak var datePick : UIDatePicker!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func donePressed(_ sender: UIBarButtonItem) {
dateTextField.resignFirstResponder()
dateTextLabel.resignFirstResponder()
startDateTextButton.resignFirstResponder()
endDateTextButton.resignFirstResponder()
}
func donePressedButton(_ sender :UIButton){
startDateTextButton.resignFirstResponder()
endDateTextButton.resignFirstResponder()
}
func tappedToolBarBtn(_ sender: UIBarButtonItem) {
let dateformatter = DateFormatter()
startDateTextButton.setTitle(dateformatter.string(from: Date()), for: .normal)
endDateTextButton.setTitle(dateformatter.string(from: Date()), for: .normal)
startDateTextButton.resignFirstResponder()
endDateTextButton.resignFirstResponder()
}
func closeDatePicker(){
self.view.endEditing(true)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
closeDatePicker()
}
#IBAction func BtnClicked(sender: UIButton) {
let datePickerView: UIDatePicker = UIDatePicker()
datePickerView.datePickerMode = UIDatePickerMode.date
//sender.inputView = datePickerView
self.view.addSubview(datePickerView)
datePickerView.addTarget(self, action: #selector(ViewController.datePickerValueChanged), for: UIControlEvents.valueChanged)
self.whoTriggeredPickerView = sender
}
func datePickerValueChanged(_ sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = DateFormatter.Style.medium
dateFormatter.timeStyle = DateFormatter.Style.none
//dateTextField.text = dateFormatter.string(from: sender.date)
if self.whoTriggeredPickerView == startDateTextButton {
// set startDateBtn title
startDateTextButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
}else if self.whoTriggeredPickerView == endDateTextButton {
// set endDateBtn title
endDateTextButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
}
}
}
For your question:
You can create a tmp variable to record picker is triggered by which button. Then you can decide which button's title you want to set.
//In your view controller:
var whoTriggeredPickerView: UIButton?
//In your BtnClicked function:
self.whoTriggeredPickerView = sender
//Then in datePickerValueChanged function:
if self.whoTriggeredPickerView == StartDateButton {
// set startDateBtn title
}else if self.whoTriggerPickerView == endDateButton {
// set endDateBtn title
}
Some suggestions:
always lowercase the instance name, you are using DatePick as the instance name of UIDatePicker, instead, you should name it as datePicker; for function names as well.
for your case you can use two textfields instead of buttons.
let datePicker = UIDatePicker()
datePicker.addTarget(self, action: #selector(dateChanged), for: .valueChanged)
startDateTextField.inputView = datePicler
endDateTextField.inputView = datePicker
I have a problem with a little App I am programming & learning with. I finally added a sound that it's supposed to play when you press a Stepper, and it does, the problem is that it's also playing when you press a different button.
I honestly have no idea why it happens and how to fix it.
Any help?
here's my code:
ViewONE (here the button that shouldn't be playing the audio file is "BotonLetsBegin"
//
// ViewONE.swift
// Simple Score
//
// Created by Juan Francisco Mellado on 10/1/15.
// Copyright © 2015 Juan Francisco Mellado. All rights reserved.
//
import Foundation
import UIKit
class ViewONE: UIViewController, UITextFieldDelegate {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
#IBOutlet weak var ScrollViewOne: UIScrollView!
#IBOutlet weak var teamRedName: UITextField!
#IBOutlet weak var initScoreRed: UITextField!
#IBAction func BotonLetsBeginAction(sender: UIButton) {
}
#IBOutlet weak var BotonLetsBegin: UIButton!
#IBOutlet weak var teamBlueName: UITextField!
#IBOutlet weak var initScoreBlue: UITextField!
#IBAction func userTappedBackground(sender: AnyObject) {
view.endEditing(true)
}
#IBAction func userTappedThisView(sender: AnyObject) {
view.endEditing(true)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestViewController : ViewTwo = segue.destinationViewController as! ViewTwo
// Cambiamos los valores si no existen a 0
if(initScoreRed.text == ""){
initScoreRed.text = "0"
}
if(initScoreBlue.text == ""){
initScoreBlue.text = "0"
}
//---------
DestViewController.RedName = teamRedName.text!
DestViewController.BlueName = teamBlueName.text!
DestViewController.RedScore = initScoreRed.text!
DestViewController.BlueScore = initScoreBlue.text!
}
override func viewDidLoad() {
super.viewDidLoad()
let value = UIInterfaceOrientation.Portrait.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
BotonLetsBegin.layer.cornerRadius = 5
self.initScoreBlue.delegate = self
self.initScoreRed.delegate = self
}
let TEXT_FIELD_LIMIT = 3
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
switch textField {
case initScoreBlue:
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= TEXT_FIELD_LIMIT
case initScoreRed:
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= TEXT_FIELD_LIMIT
case teamBlueName:
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= 25
case teamRedName:
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= 25
default:
return true
}
}
//For Swift 1.2
//return count((textField.text ?? "").utf16) + count(string.utf16) - range.length <= TEXT_FIELD_LIMIT
override func shouldAutorotate() -> Bool {
return false
}
func textFieldDidBeginEditing(textField: UITextField) {
if (textField == initScoreBlue){
ScrollViewOne.setContentOffset(CGPointMake(0, 250), animated: true)
}
if (textField == teamBlueName){
ScrollViewOne.setContentOffset(CGPointMake(0, 250), animated: true)
}
if (textField == teamRedName){
ScrollViewOne.setContentOffset(CGPointMake(0, 100), animated: true)
}
if (textField == initScoreRed){
ScrollViewOne.setContentOffset(CGPointMake(0, 100), animated: true)
}
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField: UITextField) {
ScrollViewOne.setContentOffset(CGPointMake(0, 0), animated: true)
}
// - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
//{
//[textField resignFirstResponder];
//}
}
Here is ViewTwo where the steppers are
//
// ViewTWO.swift
// Simple Score
//
// Created by Juan Francisco Mellado on 9/29/15.
// Copyright © 2015 Juan Francisco Mellado. All rights reserved.
//
import Foundation
import UIKit
import AVFoundation
class ViewTwo : UIViewController, AVAudioPlayerDelegate {
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
/// Actions de sonido -------
#IBAction func stepperSoundRed(sender: UIStepper) {
audioPlayer.play()
}
#IBAction func stepperSoundBlue(sender: UIStepper) {
audioPlayer.play()
}
/////////////////////////
// el nombre del equipo Blue
#IBOutlet weak var teamBlueTextLabel: UILabel!
var BlueName = String()
// nombre del equipo rojo
#IBOutlet weak var teamRedTextLabel: UILabel!
var RedName = String()
// score inicial del equipo rojo
#IBOutlet weak var RedScoreLabel: UILabel!
var RedScore = String()
// score initcial equipo azul
#IBOutlet weak var BlueScoreLabel: UILabel!
var BlueScore = String()
// que funcionen los Steppers
#IBOutlet weak var RedStepperUI: UIStepper!
#IBOutlet weak var BlueStepperUI: UIStepper!
// Botón Done
#IBOutlet weak var BotonDone: UIButton!
#IBAction func BlueStepperValueChange(sender: UIStepper) {
BlueScoreLabel.text = Int(sender.value).description
}
#IBAction func RedStepperValueChange(sender: UIStepper) {
RedScoreLabel.text = Int(sender.value).description
}
// archivos de sonidos
var sound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("ping2", ofType: "mp3")!)
var audioPlayer = AVAudioPlayer()
required init? (coder aDecoder: NSCoder){
super.init(coder: aDecoder)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: sound, fileTypeHint: nil)
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.play()
} catch {
// Errors here
}
}
// VIEW DID LOAD
override func viewDidLoad() {
// No se para que es esto??
super.viewDidLoad()
BotonDone.layer.cornerRadius = 5
// Checamos si están vacios los nombres
if BlueName.isEmpty {
BlueName = "TEAM BLUE"
}
if RedName.isEmpty {
RedName = "TEAM RED"
}
// Proseguimos a asignarlos a las Labels
teamBlueTextLabel.text = BlueName
teamRedTextLabel.text = RedName
RedScoreLabel.text = RedScore
BlueScoreLabel.text = BlueScore
// Aqui vemos el Red +/-
RedStepperUI.wraps = true
RedStepperUI.autorepeat = false
RedStepperUI.value = Double(RedScore)!
RedStepperUI.maximumValue = 999
// aqui vemos el Blue +/-
BlueStepperUI.wraps = true
BlueStepperUI.autorepeat = false
BlueStepperUI.value = Double(BlueScore)!
BlueStepperUI.maximumValue = 999
//
let value = UIInterfaceOrientation.LandscapeLeft.rawValue
UIDevice.currentDevice().setValue(value, forKey: "orientation")
}
override func shouldAutorotate() -> Bool {
return false
}
}
Your issue is in init(...) method of ViewTwo. You create a player and call the play() method :
required init? (coder aDecoder: NSCoder){
super.init(coder: aDecoder)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: sound, fileTypeHint: nil)
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.play() // this is your problem ;)
} catch {
// Errors here
}
}