Don't dismiss keyboard on UITextField return - ios

Every post I've seen wants to dismiss the keyboard once user is finished editing a UITextField. I find it annoying to have to tap in the UITextField each time I want to begin editing again to get the keyboard to reappear. I need to keep the keyboard up at all times so the next user input begins as soon as typing starts.
Currently my code can only act on user keyboard input if the UITextField resignFirstResponder method is called in the textFieldShouldReturn -> true UITextFieldDelegate method.
I tried the following method:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
false
}
//
// TestViewController.swift
// Hyperpolyglot
//
// Created by Alan Dripps on 10/02/2019.
// Copyright © 2019 Alan Dripps. All rights reserved.
//
import UIKit
import AVFoundation
class TestViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var prompt: UILabel!
#IBOutlet weak var synthesizeButton: UIButton!
#IBOutlet weak var instructions: UILabel!
#IBOutlet weak var englishAnswer: UITextField!
var words = [String]()
var testWords = [String]()
var useHomework: Bool!
var practiceWrongNoCorrectAnswer: Int!
var homeworkWrongNoCorrectAnswer: Int!
var questionCounter = 0
var showingQuestion = true
var chosenLanguage = String()
var language = String()
let wordsString = "Words"
var englishWord = String()
var foreignWord = String()
var attempted = Int()
var homeworkAttempted = Int()
var homework = Int()
override func viewDidLoad() {
super.viewDidLoad()
print("viewDidLoad questionCounter is: \(questionCounter)")
print("useHomework in viewDidLoad in TestViewController is: \(useHomework!)")
loadChosenLanguage()
loadWords()
print("testWords just before shuffle: \(testWords)")
testWords.shuffle()
print("testWords just after shuffle: \(testWords)")
if useHomework == true {
navigationItem.title = "Learn Homework"
} else {
navigationItem.title = "Learn \(chosenLanguage.capitalized)"
}
navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "Reveal Answer", style: .plain , target: self, action: #selector(answerTapped))
stackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
stackView.alpha = 0
synthesizeButton.isEnabled = false
synthesizeButton.alpha = 0.25
englishAnswer.delegate = self
englishAnswer.layer.cornerRadius = 0.05 * englishAnswer.bounds.size.width
let placeholderColor = UIColor.systemGray
englishAnswer.attributedPlaceholder = NSAttributedString(string: "Tap here to answer", attributes: [NSAttributedString.Key.foregroundColor : placeholderColor])
askQuestion()
instructions.isHidden = true
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//loadWords()
//print("testWords just before shuffle: \(testWords)")
//testWords.shuffle()
//print("testWords just after shuffle: \(testWords)")
//print("questionCounter in viewWillAppear in TestViewController is: \(questionCounter)")
englishAnswer.delegate = self
englishAnswer.returnKeyType = .done
}
func textFieldShouldReturn(_ englishAnswer: UITextField) -> Bool {
//englishAnswer.resignFirstResponder()
self.englishAnswer(englishAnswer)
return true
}
#IBAction func synthesizeButton(_ sender: UIButton) {
let utterance = AVSpeechUtterance(string: testWords[questionCounter].components(separatedBy: "::")[0])
utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
utterance.rate = 0.5
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
}
#IBAction func englishAnswer(_ sender: UITextField) {
print("Entered englishAnswer")
let trimmed = sender.text?.trimmingCharacters(in: .whitespacesAndNewlines)
print("trimmed?capitalized in englishAnswer in TestViewController is: \(trimmed?.capitalized ?? "")")
if trimmed?.capitalized == testWords[questionCounter].components(separatedBy: "::")[0] {
resetWordCounters()
if questionCounter + 1 < testWords.count {
questionCounter += 1
} else {
questionCounter = 0
}
if case prompt.textColor = UIColor(red: 0, green: 0.7, blue: 0,
alpha: 1) {
print("prompt.textColor in englishAnswer in
TestViewController is: \(prompt.textColor!)")
}
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.prepareForNextQuestion()
}
} else {
appendPracticeWord()
prompt.textColor = UIColor(red: 0.7, green: 0, blue: 0, alpha: 1)
synthesizeButton.isEnabled = true
synthesizeButton.alpha = 1
instructions.isHidden = false
}
englishAnswer.text?.removeAll()
englishAnswer.placeholder = nil
englishAnswer.isEnabled = false
englishAnswer.layer.borderColor = UIColor.lightGray.cgColor as CGColor
}
func loadChosenLanguage() {
if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
if let savedChosenLanguage = defaults.object(forKey: "languageChosen") as? String {
print("savedChosen Language in loadChosenLanguage in TestViewController is: \(savedChosenLanguage)")
chosenLanguage = savedChosenLanguage
}
}
}
func loadWords() {
print("in loadWords in TestViewController")
if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
print("after defaults in loadWords in TestViewController")
if var savedWords = defaults.object(forKey: "words") as? [String] {
words.removeAll()
testWords.removeAll()
words = savedWords
print("savedWords in loadWords in TestViewcontroller are: \(savedWords)")
print("words in loadWords in TestViewController are: \(words)")
for savedWord in savedWords {
let split = savedWord.components(separatedBy: "::")
if useHomework == false {
if split[7] == chosenLanguage {
testWords.append(savedWord)
print("testWords.append from chosenLanguage in loadWords in TestViewController are: \(testWords)")
}
} else {
if split[6] == "1" {
testWords.append(savedWord)
print("testWords.append from homework in loadWords in TestViewController are: \(testWords)")
}
}
}
savedWords.removeAll()
}
}
}
override func viewDidLayoutSubviews() {
//configureButtons()
}
func configureButtons() {
synthesizeButton.layer.cornerRadius = 0.5 * synthesizeButton.bounds.size.width
synthesizeButton.layer.borderColor = UIColor.lightGray.cgColor as CGColor
synthesizeButton.layer.borderWidth = 4.0
synthesizeButton.clipsToBounds = true
}
func resetWordCounters() {
print("questionCounter in resetWordCounters in TestViewController is: \(questionCounter)")
let resetCountersWord = testWords[questionCounter].components(separatedBy: "::")[1]
print("resetCountersWord in resetWordCounters in TestViewController is: \(resetCountersWord)")
if useHomework == false {
attempted += 1
} else {
homeworkAttempted += 1
}
if useHomework == false {
practiceWrongNoCorrectAnswer = 0
} else {
homeworkWrongNoCorrectAnswer = 0
}
var indexForTestWord = 0
for testWord in testWords {
if testWord.components(separatedBy: "::")[1] == resetCountersWord {
print("testWord equals resetCountersWord: \(resetCountersWord)")
let split = testWord.components(separatedBy: "::")
let firstWord = split[0]
let secondWord = split[1]
testWords.remove(at: indexForTestWord)
print("testWords.remove in resetWordCounters in TestViewController are: \(testWords)")
testWords.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(practiceWrongNoCorrectAnswer!)"+"::"+"\(homeworkWrongNoCorrectAnswer!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForTestWord)
print("testWords.insert in resetWordCounters in TestViewController: \(testWords)")
break
}
indexForTestWord += 1
}
print("indexForTestWord in resetWordCounters in TestViewController is: \(indexForTestWord)")
removeInsertWord(resetCountersWordPracticeWord: resetCountersWord)
}
func appendPracticeWord() {
if useHomework == false {
attempted += 1
} else {
homeworkAttempted += 1
}
if useHomework == false {
practiceWrongNoCorrectAnswer += 1
} else {
homeworkWrongNoCorrectAnswer += 1
}
print("questionCounter in appendPracticeWord in TestViewController is: \(questionCounter)")
let practiceWord = testWords[questionCounter].components(separatedBy: "::")[1]
print("practiceWord in appendPracticeWord in TestViewController is: \(practiceWord)")
var indexForPracticeWord = 0
for testWord in testWords {
if testWord.components(separatedBy: "::")[1] == practiceWord {
print("testWord equals practiceWord: \(practiceWord)")
let split = testWord.components(separatedBy: "::")
let firstWord = split[0]
let secondWord = split[1]
testWords.remove(at: indexForPracticeWord)
print("testWords.remove in appendPracticeWord in TestViewController: \(testWords)")
testWords.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(practiceWrongNoCorrectAnswer!)"+"::"+"\(homeworkWrongNoCorrectAnswer!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForPracticeWord)
print("testWords.insert in appendPracticeWord in TestViewController: \(testWords)")
break
}
indexForPracticeWord += 1
}
print("indexForPracticeWord in appendPracticeWord in TestViewController is: \(indexForPracticeWord)")
removeInsertWord(resetCountersWordPracticeWord: practiceWord)
}
func removeInsertWord(resetCountersWordPracticeWord: String) {
var indexForWord = 0
for word in words {
if word.components(separatedBy: "::")[1] == resetCountersWordPracticeWord {
print("word equals resetCountersWordPracticeWord: \(word)")
let split = word.components(separatedBy: "::")
let firstWord = split[0]
let secondWord = split[1]
words.remove(at: indexForWord)
print("words.remove in removeInsertWord in TestViewController are: \(words)")
words.insert("\(firstWord)"+"::"+"\(secondWord)"+"::"+"\(practiceWrongNoCorrectAnswer!)"+"::"+"\(homeworkWrongNoCorrectAnswer!)"+"::"+"\(attempted)"+"::"+"\(homeworkAttempted)"+"::"+"\(homework)"+"::"+"\(language)", at: indexForWord)
print("words.insert in removeInsertWord in TestViewController after insert: \(words)")
break
}
indexForWord += 1
}
print("indexForWord in removeInsertWord in TestViewController is: \(indexForWord)")
saveWords()
}
#objc func answerTapped() {
showEnglishQuestion()
}
func showEnglishQuestion() {
showingQuestion = !showingQuestion
if showingQuestion {
// we should be showing the question – reset!
prepareForNextQuestion()
navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "Reveal Answer", style: .plain , target: self, action: #selector(answerTapped))
//englishAnswer.isEnabled = true
} else {
// we should be showing the answer – show it now, and set the color to be green
print("questionCounter before prompt.text in showEnglishQuestion in TestViewController is: \(questionCounter)")
prompt.text = testWords[questionCounter].components(separatedBy: "::")[0]
prompt.textColor = UIColor(red: 0, green: 0.7, blue: 0, alpha: 1)
print("testWords.count in askQuestion in TestViewController is: \(testWords.count)")
if questionCounter + 1 < testWords.count {
let showNextLanguage = testWords[questionCounter + 1] .components(separatedBy: "::")[7]
navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "Next \(showNextLanguage.capitalized) Word", style: .plain , target: self, action: #selector(answerTapped))
print("showNextLanguage in showEnglishQuestion in TestViewController is: \(showNextLanguage)")
// move the question counter one place
questionCounter += 1
print("questionCounter in showEnglishQuestion in TestViewController is: \(questionCounter)")
} else {
// wrap it back to 0 if we've gone beyond the size of the array
questionCounter = 0
print("questionCounter in questionCounter = 0 else statement in showEnglishQuestion in TestViewController is: \(questionCounter)")
print("words array in showEnglishQuestion in TestViewController when questionCounter = 0 is: \(testWords)")
let showNextLanguage = testWords[questionCounter] .components(separatedBy: "::")[7]
navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "Next \(showNextLanguage.capitalized) Word", style: .plain , target: self, action: #selector(answerTapped))
print("showNextLanguage in if questionCounter == 0 in showEnglishQuestion in TestViewController is: \(showNextLanguage)")
// move the question counter one place
}
englishAnswer.isEnabled = false
}
synthesizeButton.isEnabled = false
synthesizeButton.alpha = 0.25
instructions.isHidden = true
}
func askQuestion() {
// pull out the foreign word at the current question position
print("questionCounter before prompt.text in askQuestion in TestViewController is: \(questionCounter)")
prompt.text = testWords[questionCounter].components(separatedBy: "::")[1]
print("testWords[questionCounter] in askQuestion in TestViewController is: \(testWords[questionCounter].components(separatedBy: "::")[0])")
let animation = UIViewPropertyAnimator(duration: 0.5, dampingRatio: 0.5) {
self.stackView.alpha = 1
self.stackView.transform = CGAffineTransform.identity
}
animation.startAnimation()
englishAnswer.isEnabled = true
englishAnswer.layer.borderColor = UIColor.black.cgColor as CGColor
englishWord = testWords[questionCounter].components(separatedBy: "::")[0]
foreignWord = testWords[questionCounter].components(separatedBy: "::")[1]
practiceWrongNoCorrectAnswer = Int(testWords[questionCounter].components(separatedBy: "::")[2])!
homeworkWrongNoCorrectAnswer = Int(testWords[questionCounter].components(separatedBy: "::")[3])!
attempted = Int(testWords[questionCounter].components(separatedBy: "::")[4])!
homeworkAttempted = Int(testWords[questionCounter].components(separatedBy: "::")[5])!
homework = Int(testWords[questionCounter].components(separatedBy: "::")[6])!
language = testWords[questionCounter].components(separatedBy: "::")[7]
}
func prepareForNextQuestion() {
let animation = UIViewPropertyAnimator(duration: 0.5, curve: .easeInOut) { [unowned self] in
self.stackView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
self.stackView.alpha = 0
}
animation.addCompletion { [unowned self] position in
self.prompt.textColor = UIColor.black
self.askQuestion()
}
animation.startAnimation()
englishAnswer.placeholder = "Tap here to answer"
}
func saveWords() {
if let defaults = UserDefaults(suiteName: "group.co.uk.tirnaelectronics.hyperpolyglot.todayview") {
defaults.set(words, forKey: "words")
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
The textFieldShouldEndEditing method stopped the keyboard dismissing, but didn't pass on the user's edit to my sender: UITextField method that does something useful with the input.

Please add textfield's delegate to view controller in storyboard or set "englishAnswer.delegate = self" in viewDidLoad
Don't try return false in textFieldShouldReturn. It won't make any change.

Now I got your point clearly. All you have done is correct. Then manually call the method "self.englishAnswer (_ sender: textField) in textFieldShouldEndEditing or textFieldShouldReturn. Then it will work. You haven't call the method anywhere.

Related

Child View Controllers in Page View Controller Failing to Receive Delegate Calls

I am having an issue with my two child view controllers inside a parent PageViewController, where a delegate called by one of the children is not received by the other child.
My first child contains buttons, and when a button is pressed, a delegate is triggered in the other child to pause the timer. However, it fails to receive the call and the timer continues to run.
Here is my PageViewController:
class StartMaplessWorkoutPageViewController: UIPageViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
lazy var workoutViewControllers: [UIViewController] = {
return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
}()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.dataSource = self
// Saw this from another answer, doesn't do anything that helps (at the moment)
let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayMaplessViewController") as! DisplayMaplessViewController
buttonsViewController.buttonsDelegate = displayMaplessViewController
if let firstViewController = workoutViewControllers.last {
setViewControllers([firstViewController], direction: .forward, animated: true, completion: nil)
}
let pageControl = UIPageControl.appearance(whenContainedInInstancesOf: [StartWorkoutPageViewController.self])
pageControl.currentPageIndicatorTintColor = .orange
pageControl.pageIndicatorTintColor = .gray
}
func getNewViewController(viewController: String) -> UIViewController {
return (storyboard?.instantiateViewController(withIdentifier: viewController))!
}
// MARK: PageView DataSource
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return workoutViewControllers.last
}
guard workoutViewControllers.count > previousIndex else {
return nil
}
return workoutViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let workoutViewControllersCount = workoutViewControllers.count
guard workoutViewControllersCount != nextIndex else {
return workoutViewControllers.first
}
guard workoutViewControllersCount > nextIndex else {
return nil
}
return workoutViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return workoutViewControllers.count
}
func presentationIndex(for pageViewController: UIPageViewController) -> Int {
guard let firstViewController = viewControllers?.first, let firstViewControllerIndex = workoutViewControllers.firstIndex(of: firstViewController) else {
return 0
}
return firstViewControllerIndex
}
}
My ChildViewController with Buttons:
protocol ButtonsViewDelegate: class {
func onButtonPressed(button: String)
}
class ButtonsViewController: UIViewController {
weak var buttonsDelegate: ButtonsViewDelegate?
var isPaused: Bool = false
#IBOutlet weak var startStopButton: UIButton!
#IBOutlet weak var optionsButton: UIButton!
#IBOutlet weak var endButton: UIButton!
#IBAction func startStopButton(_ sender: Any) {
if isPaused == true {
buttonsDelegate?.onButtonPressed(button: "Start")
isPaused = false
} else {
buttonsDelegate?.onButtonPressed(button: "Pause")
isPaused = true
}
}
#IBAction func endButton(_ sender: Any) {
let menu = UIAlertController(title: "End", message: "Are you sure you want to end?", preferredStyle: .actionSheet)
let end = UIAlertAction(title: "End", style: .default, handler: { handler in
self.buttonsDelegate?.onButtonPressed(button: "End")
})
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
menu.addAction(end)
menu.addAction(cancelAction)
self.present(menu, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
}
}
My other ChildViewController, which should be receiving the calls of the ButtonsViewDelegate:
import UIKit
class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {
var timer = Timer()
var currentTime: TimeInterval = 0.0
var isCountdown: Bool = false
var isInterval: Bool = false
var currentRepeats: Int = 0
var currentActivity: Int = 0
var count: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
startIntervalTimer(withTime: 0)
}
// Currently not being called
func onButtonPressed(button: String) {
switch button {
case "Start":
restartIntervalTimer()
case "Pause":
pauseIntervalTimer()
case "End":
stop()
default:
break
}
}
func startIntervalTimer(withTime: Double) {
if withTime != 0 {
currentTime = withTime
if isInterval != true {
isCountdown = true
}
}
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
}
func pauseIntervalTimer() {
timer.invalidate()
}
func restartIntervalTimer() {
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(intervalTimerUpdate), userInfo: nil, repeats: true)
}
// Currently Not being called
func stop() {
timer.invalidate()
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.hour, .minute, .second]
formatter.zeroFormattingBehavior = [.pad]
let timeString = formatter.string(from: currentTime)
// save the data etc
print("Stop is called")
}
#objc func intervalTimerUpdate() {
currentTime += 1.0
print(currentTime)
}
}
Sorry that this is so long winded, been trying for quite a while and really annoyed that it doesn't work! Thanks!
I'll try to be clear, hopefully i'll be so as english is not my native language.
It seems to me that you are instantiating your ViewControllers to be presented in the getNewViewController() method and storing them in the workoutViewControllers array, but you are setting the delegate as a separate instance that you never set in your PageVC. You need to set the delegates using the same instances.
These two are two instances of two VC classes (also not sure if the identifier "DisplayViewController" is right, i expected "DisplayMaplessViewController", hard to tell without the storyboard):
let buttonsViewController = storyboard?.instantiateViewController(withIdentifier: "ButtonsViewController") as! ButtonsViewController
let displayMaplessViewController = storyboard?.instantiateViewController(withIdentifier: "DisplayViewController") as! DisplayMaplessViewController
buttonsViewController.buttonsDelegate = displayMaplessViewController
And these in the array two other instances, unrelated from the ones above, of the same two classes:
lazy var workoutViewControllers: [UIViewController] = {
return [self.getNewViewController(viewController: "ButtonsViewController"), self.getNewViewController(viewController: "DisplayMaplessViewController")]
}()
To better understand what i mean, i refactored from scratch and semplified your project (had to do it programmatically as i'm not used to storyboards).
It now consists of a PageController that displays a buttonsVC with a red button and a displayMaplessVC with a blue background.
Once you press the red button, the delegate method is called which causes the blue background to turn green.
Take a look at what i'm doing, as i'm appending the same instances of which i set the delegate:
instantiate a DisplayMaplessViewController object and ButtonsViewController object;
set buttonsVC.buttonsDelegate = displayMaplessVC;
append both ViewControllers to the array.
This is a way to get it done but for sure there are several other ways to achieve the same result, once you get the point and understand your mistake you can pick the one you like the most.
Just copy and paste it into a new project, build and run (you have to set the class of the starting ViewController in the Storyboard as StartMaplessWorkoutPageViewController):
import UIKit
class StartMaplessWorkoutPageViewController: UIViewController, UIPageViewControllerDelegate, UIPageViewControllerDataSource {
private var workoutViewControllers = [UIViewController]()
private let pageController: UIPageViewController = {
let pageController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
return pageController
}()
override func viewDidLoad() {
super.viewDidLoad()
pageController.delegate = self
pageController.dataSource = self
let buttonsVC = ButtonsViewController()
let displayMaplessVC = DisplayMaplessViewController()
buttonsVC.buttonsDelegate = displayMaplessVC
workoutViewControllers.append(buttonsVC)
workoutViewControllers.append(displayMaplessVC)
self.addChild(self.pageController)
self.view.addSubview(self.pageController.view)
self.pageController.setViewControllers([displayMaplessVC], direction: .forward, animated: true, completion: nil)
self.pageController.didMove(toParent: self)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
pageController.view.frame = view.bounds
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let previousIndex = viewControllerIndex - 1
guard previousIndex >= 0 else {
return workoutViewControllers.last
}
guard workoutViewControllers.count > previousIndex else {
return nil
}
return workoutViewControllers[previousIndex]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let viewControllerIndex = workoutViewControllers.firstIndex(of: viewController) else {
return nil
}
let nextIndex = viewControllerIndex + 1
let workoutViewControllersCount = workoutViewControllers.count
guard workoutViewControllersCount != nextIndex else {
return workoutViewControllers.first
}
guard workoutViewControllersCount > nextIndex else {
return nil
}
return workoutViewControllers[nextIndex]
}
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return workoutViewControllers.count
}
}
.
protocol ButtonsViewDelegate: class {
func onButtonPressed()
}
import UIKit
class ButtonsViewController: UIViewController {
weak var buttonsDelegate: ButtonsViewDelegate?
let button: UIButton = {
let button = UIButton()
button.backgroundColor = .red
button.addTarget(self, action: #selector(onButtonPressed), for: .touchUpInside)
return button
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(button)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
button.frame = CGRect(x: 50,
y: 50,
width: 100,
height: 100)
}
#objc private func onButtonPressed() {
buttonsDelegate?.onButtonPressed()
}
}
.
import UIKit
class DisplayMaplessViewController: UIViewController, ButtonsViewDelegate {
private let testView: UIView = {
let view = UIView()
view.backgroundColor = .blue
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(testView)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
testView.frame = view.bounds
}
internal func onButtonPressed() {
testView.backgroundColor = .green
}
}

onClick event in a for loop (Swift) not working

Data sharing between two view controllers by using segue is not working in for loop. I am using Xcode9 and Swift
swift4
ViewController.swift
import UIKit
import SwiftyJSON
import Alamofire
class ViewController: UIViewController {
var distnames = [String]()
var distcodes = [String]()
var data1: [AnyObject] = []
var values = [String]()
var values1 = [String]()
var current_arr :[String] = []
var ofcnames = " ";
var ofccodes = " ";
var code = " ";
var testtext = "";
var id = "";
var tiTle = "";
var releaseYear = "";
#IBOutlet weak var Verticalstackview: UIStackView!
#IBOutlet weak var scrollview: UIScrollView!
#IBOutlet weak var testlabel: UILabel!
#IBOutlet weak var mainView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
print("code is \(code)")
self.values = [String]()
self.values1 = [String]()
self.ofcnames = "";
self.ofccodes = "";
Alamofire.request("https://facebook.github.io/react-native/movies.json")
.responseJSON { (response) in
if ((response.result.value) != nil) {
let jsondata = JSON(response.result.value!)
// print(jsondata)
if let da = jsondata["movies"].arrayObject
{
self.data1 = da as [AnyObject]
print("resp is \(da) ")
}
print("respcode count is \(self.data1.count) ")
self.Verticalstackview.height(constant: 40)
self.mainView.height(constant: CGFloat(40 * self.data1.count))
self.scrollview.height(constant: CGFloat(40 * self.data1.count))
for distics in self.data1 {
if let resp = distics as? NSDictionary {
self.id = resp["id"] as! String
self.tiTle = resp["title"] as! String
self.releaseYear = resp["releaseYear"] as! String
let stackView = UIStackView()
stackView.height(constant: 40)
stackView.distribution = .fill
stackView.alignment = .fill
stackView.axis = .horizontal
stackView.spacing = 5
let lblId = UILabel()
lblId .text = "\(self.id)"
lblId .font = UIFont(name: "verdana", size: 15.0)
lblId .textAlignment = .center
lblId .textColor = .gray
lblId .numberOfLines = 0
lblId .width(constant: 55)
let lbltiTle = UILabel()
lbltiTle.text = "\(self.tiTle)"
lbltiTle.font = UIFont(name: "verdana", size: 15.0)
lbltiTle.textAlignment = .left
lbltiTle.textColor = .black
lbltiTle.numberOfLines = 0
lbltiTle.width(constant: 120)
let tap = UITapGestureRecognizer.init(target:self,action: #selector(self.tapFunction) )
lbltiTle.isUserInteractionEnabled = true
lbltiTle.addGestureRecognizer(tap)
self.testtext = lbltiTle.text!
let lblRYear = UILabel()
lblRYear.text = "\(self.releaseYear)"
lblRYear.font = UIFont(name: "verdana", size: 15.0)
lblRYear.textAlignment = .right
lblRYear.textColor = .black
lblRYear.numberOfLines = 0
lblRYear.width(constant: 100)
stackView.addArrangedSubview(lblId )
stackView.addArrangedSubview(lbltiTle)
stackView.addArrangedSubview(lblRYear)
self.Verticalstackview.addArrangedSubview(stackView)
}
}
}
print(self.values1)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#objc func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
self.performSegue(withIdentifier: "Segue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let secondcntroller = segue.destination as! SecondViewController
secondcntroller.mystring = self.testtext
}
}
extension UIView{
func height(constant : CGFloat){
setConstraint(value: constant,attribute: .height)
}
func width(constant : CGFloat){
setConstraint(value: constant,attribute: .width)
}
private func removeConstraint(attribute: NSLayoutAttribute){
constraints.forEach {
if $0.firstAttribute == attribute
{
removeConstraint($0)
}
}
}
private func setConstraint(value:CGFloat ,attribute: NSLayoutAttribute){
removeConstraint(attribute: attribute)
let Constraint =
NSLayoutConstraint(item: self,
attribute: attribute,
relatedBy: NSLayoutRelation.equal,
toItem: nil,
attribute: NSLayoutAttribute.notAnAttribute,
multiplier: 1,
constant: value)
self.addConstraint(Constraint)
}
}
SecondViewController.swift
import UIKit
class SecondViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var mystring = String()
override func viewDidLoad() {
super.viewDidLoad()
label.text=mystring
}
}
When I click the movie name text, I want that to be displayed on the second view using segue.
https://i.stack.imgur.com/dvm8j.png
But always it displays the last movie name. Please assist me sorting this issue.
https://i.stack.imgur.com/UqyqM.png
You should update your tapFunction
#objc func tapFunction(sender:UITapGestureRecognizer) {
let touchedLabel = sender.view as! UILabel
self.testtext = touchedLabel.text
self.performSegue(withIdentifier: "Segue", sender: self)
}
Hope it helps
EDIT
Now as you wants to pass the multiple values, what you can do is:
First change your for in loop from
for distics in self.data1 {
To
for (index, distics) in self.data1.enumerated() {
Now, set the tag to your label below this line
lbltiTle.addGestureRecognizer(tap)
// Add tag to your label
lbltiTle.tag = index
//**TIP: You should name the variables in camelCase format**
Now modify your function, that detects the tap on label and get the object with index being passed with the label in form of it's tag. Modify it to.
#objc func tapFunction(sender:UITapGestureRecognizer) {
let touchedLabel = sender.view as! UILabel
let touchedIndex = touchedLabel.tag
let selectedDict: NSDictionary = self.data1[touchedIndex]
print("selectedDict:\(selectedDict)")
self.performSegue(withIdentifier: "Segue", sender: self)
}
You can print the required values from it.
NOTE: For the pure swift approach, you should Avoid using NSDictionary, better use [String: Any] which is a key value pair.
Hope it helps
The self.testtext property from ViewController is not set properly. It should be set every time the tapFunction() is called.
Any way I recomand you to use UITabiewViewController and a data source array which should hold all your elements to display.
Issue
That is because you are passing testtext as a parameter to the next ViewController.mystring. testtext is being changed every time on the loop and the last time it is being changed it is on the last element. Therefore you need to save an index to know where you pressed.
Solution
Change your for loop to get the index of the element you're iterating as above
for index in 0..<self.data1.count {
let distics = self.data1[index]
// Other lines of code
lbltiTle.tag = index
}
Change your handler as below
#objc func tapFunction(sender:UITapGestureRecognizer) {
print("tap working")
guard let index = sender.view?.tag,
let title = (self.data1[index] as? NSDictionary)["title"] else { return }
testtext = title
self.performSegue(withIdentifier: "Segue", sender: self)
}
Add one more function for your label.
lbltiTle.target(forAction: onClickLabel, withSender:)
Definition -
func onClickLabel(sender: UILabel) {
self.testtext = sender.text
}
Call this in your tap function

Incorrect height of tableview cell XIB in UITableView for first time?

I am having hard time resolving one issue. I have a tableview which has many prototype cells. One of them i am using a tagview to show multiple tags selected. So i have used a XIB file which has a TAGVIEW as subview of it & I have used that XIB in tableview cell. Now when i first load the tableview and scroll down then height of cell is large but when i scroll down and up then it's fits in the size of tags. I have tried below solutions for the but not of them worked.
I tried solutions:
1.cell.layoutIfNeeded before return cell.
2.cell.layoutSubView
3.cell.tagView.layoutIfNeeded
4.cell.setNeedsLayout()
5.Relaod tableview in ViewDidAppear
6.In tagViewCellXIB, in AwakeForNib method added self.layoutIfNeeded.
7.In tagViewCellXIB,Override didMoveToParent() & added self.layoutIfNeeded.
I have given estimated row height already.
Here is tagViewCell Class
class TagViewCell: UITableViewCell {
#IBOutlet weak var tagView: TagListView!
#IBOutlet weak var textfield: UITextField!
#IBOutlet weak var titleLbl: UILabel!
#IBOutlet weak var heightTagViewConstraint : NSLayoutConstraint!
#IBOutlet weak var lblValidation: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
// Initialization code
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
self.layoutIfNeeded()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
self.heightTagViewConstraint.constant = 35
}
func configureView(type: TagCellType){
switch type {
case .Language:
setupLanguageCell()
titleLbl.text = "Language"
textfield.isHidden = true
if UserData.userData.language.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What languages do you speak?"
}
break
default:
textfield.isHidden = true
setupTagCell(tagType: type)
if type == .preferArea {
seUpPreffredAreaValidation()
titleLbl.text = "Preferred areas"
if UserData.userData.preferAreaArr.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What areas do you prefer?"
}
}
else if type == .City{
titleLbl.text = "Preferred cities"
if UserData.userData.cities.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What city do you prefer? (Optional)"
}
}else{
titleLbl.text = "Preferred countries"
if UserData.userData.country.count == 0{
tagView.isHidden = true
textfield.isHidden = false
textfield.placeholder = "What country do you prefer? (Optional)"
}
}
break
}
}
/// Set up tag view
func setupLanguageCell() {
tagView.removeAllTags()
tagView.tag = 1003 // For Language field
for (index, elememt) in UserData.userData.language.enumerated() {
print(index)
if let langDict = elememt as? NSDictionary{
tagView.isHidden = false
let languageTagView = tagView.addTag(langDict.value(forKey: "lang_name") as! String)
languageTagView.tagBackgroundColor = UIColor.clear
languageTagView.textColor = .black
languageTagView.textFont = UIFont.systemFont(ofSize: 17)
languageTagView.paddingX = -1
languageTagView.isUserInteractionEnabled = false
let levelTagView = tagView.addTag(Constants.languageLevel[langDict.value(forKey: "level") as! Int])
levelTagView.frame = CGRect(x: levelTagView.frame.origin.x, y: (languageTagView.frame.height / 2) - (levelTagView.frame.height / 2) + 2, width: levelTagView.frame.width, height: levelTagView.frame.height)
levelTagView.layoutIfNeeded()
levelTagView.layoutSubviews()
levelTagView.tagBackgroundColor = UIColor().textOrange()
levelTagView.textFont = UIFont.systemFont(ofSize: 10)
levelTagView.cornerRadius = 6
print("pading yyyyyy \(String(describing: tagView.rowViews.last?.frame.minY))")
if UserData.userData.language.count == index+1{
if (tagView.rowViews.last?.frame.minY)! == 48{
levelTagView.frame.origin.y = (languageTagView.frame.height / 2) - (levelTagView.frame.height / 2) + 8
}
}
}
}
if UserData.userData.language.count == 0 {
self.lblValidation.isHidden = false
} else {
self.lblValidation.isHidden = true
}
}
func seUpPreffredAreaValidation() {
if UserData.userData.preferAreaArr.count == 0 {
self.lblValidation.isHidden = false
} else {
self.lblValidation.isHidden = true
}
}
/// setup cell for country and cities
///
/// - Parameter tagType: type of cell country or cities
func setupTagCell(tagType:TagCellType) {
tagView.removeAllTags()
var tagArray:[String] = []
tagArray = UserData.userData.country
tagView.tag = 1001 // For countries field
if tagType == .City {
tagArray = []
tagArray = UserData.userData.cities
tagView.tag = 1002 // For city field
}
if tagType == .preferArea {
tagArray = []
tagArray = UserData.userData.preferAreaArr
tagView.tag = 1003
}
var tagValue = ""
for (_, elememt) in tagArray.enumerated() {
tagView.isHidden = false
print(elememt)
if elememt.characters.count > 17 {
let index = elememt.index(elememt.startIndex, offsetBy: 16)
tagValue = elememt.substring(to: index) + ".."
}else{
tagValue = elememt
}
let levelTagView = tagView.addTag(tagValue)
levelTagView.tagBackgroundColor = UIColor().textOrange()
tagView.textFont = UIFont.systemFont(ofSize: 17)
levelTagView.cornerRadius = 8
levelTagView.enableRemoveButton = false
levelTagView.paddingX = 6
levelTagView.paddingY = 3
}
}
}
For cellForRowAtIndexPath below code is used
let cell = self.tableView.dequeueReusableCell(withIdentifier: "tagcell", for: indexPath) as! TagViewCell
cell.configureView(type: .City)
cell.tagView.delegate = self
if UserData.userData.cities.count >= 0 && UserData.userData.cities.count <= 3 {
cell.heightTagViewConstraint.constant = 25.7
}
else if let height = cell.tagView.subviews.last?.frame.maxY {
cell.heightTagViewConstraint.constant = height + 10
}
return cell
Override layoutSubviews in your TagViewCell class like bellow
class TagViewCell: UITableViewCell {
override func layoutSubviews() {
//Set the frame of tagView here
//self.tagView.frame =
}
}
and call cell.layoutIfNeeded while returning cell. If you still get any problem please comment. we are here to help you.

TabBar tappedIcon unTappedIcon Issue

When i tapped on my TabBar's first image it should be highlighted like the below image -
enter image description here
But when i tap on the first icon it's doing nothing, tap on the second icon it's highlighting the first icon, tap on the 3rd icon highlighting it's previous icon (second icon) and so on. It might be an indexing problem. Need some help to find this issue. Thank you. My TabBar current view-
enter image description here
Here is my code -
import UIKit
class ViewController: BaseViewController {
//MARK: Outlets
#IBOutlet var warningsIcon: UIImageView!
#IBOutlet var incidentsIcon: UIImageView!
#IBOutlet var notificationsIcon: UIImageView!
#IBOutlet var myMessagesIcon: UIImageView!
#IBOutlet var warningsTitle: UILabel!
#IBOutlet var incidentsTitle: UILabel!
#IBOutlet var notificationsTitle: UILabel!
#IBOutlet var myMessagesTitle: UILabel!
#IBOutlet var containerView: UIView!
//MARK: PROPERTIES
var previousSelectedIcon:UIImageView!
var previousSeelctedTitle:UILabel!
var previousIndex:Int!
var currentIndex:Int!
var previousVc:UIViewController!
fileprivate var i = 0
open var subViewController:UIViewController?
var unTappedIcon = ["warnings","incidents","notifications","my-messages"]
var tappedIcon = ["warnings_tapped","Incidents_tapped","notifications_tapped","myMessages_tapped"]
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
// Do any additional setup after loading the view, typically from a nib.
previousVc = self;
previousSelectedIcon = warningsIcon
previousSeelctedTitle = warningsTitle
previousIndex = 0;
currentIndex = 0;
//place sub view controller if any
placeSubViewControllerIfNeeded()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//place sub view controller if needed
func placeSubViewControllerIfNeeded() -> Void{
if let vc = subViewController {
vc.view.frame = self.containerView.frame
self.addChildViewController(vc)
//vc.view.frame = self.view.frame
self.containerView.addSubview(vc.view)
self.view.sendSubview(toBack: vc.view)
}
}
//MARK: For Tab bar
func updateTapView(title:UILabel, icon:UIImageView) {
previousSeelctedTitle.textColor = UIColor(red:0.60, green:0.60, blue:0.60, alpha:1.0)
previousSelectedIcon.image = UIImage.init(named: unTappedIcon[previousIndex])
title.textColor = UIColor(red:0.26, green:0.18, blue:0.49, alpha:1.0)
icon.image = UIImage.init(named: tappedIcon[currentIndex])
}
func removePreviousViewController() {
previousVc.willMove(toParentViewController: nil)
previousVc.view.removeFromSuperview()
previousVc.removeFromParentViewController()
}
func getStoryBoardByIndentifier(identifier:String)->UIStoryboard {
return UIStoryboard.init(name: identifier, bundle: nil)
}
func showSubViewContrller(subViewController:UIViewController) {
self.addChildViewController(subViewController)
subViewController.view.frame = containerView.frame
self.containerView.addSubview(subViewController.view)
subViewController.didMove(toParentViewController: self)
previousVc = subViewController
}
//Mark:- Action button for warnings
#IBAction func onClickWarnings(_ sender: UITapGestureRecognizer) {
print("Hi there i am just a warning")
self.warningsIcon.image = UIImage.init(named: "warnings_tapped")
previousIndex = currentIndex
currentIndex = 0
updateTapView( title: warningsTitle, icon: warningsIcon)
previousSeelctedTitle = warningsTitle
previousSelectedIcon = warningsIcon
if i > 0{
removePreviousViewController()
print("i am already removed")
}
let storyBoard = getStoryBoardByIndentifier(identifier: "warnings")
let vc = storyBoard.instantiateViewController(withIdentifier: "WarningsViewController") as! WarningsViewController
showSubViewContrller(subViewController: vc)
i += 1
}
//Mark:- Action button for warnings
#IBAction func onClickIncidents(_ sender: UITapGestureRecognizer) {
print("Hi there i am just a warning")
self.incidentsIcon.image = UIImage.init(named: "Incidents_tapped")
previousIndex = currentIndex
currentIndex = 1
updateTapView( title: incidentsTitle, icon: incidentsIcon)
previousSeelctedTitle = incidentsTitle
previousSelectedIcon = incidentsIcon
if i > 0{
removePreviousViewController()
print("i am already removed")
}
let storyBoard = getStoryBoardByIndentifier(identifier: "incidents")
let vc = storyBoard.instantiateViewController(withIdentifier: "IncidentsViewController") as! IncidentsViewController
showSubViewContrller(subViewController: vc)
i += 1
}
//Mark:- Action button for warnings
#IBAction func onClickNotifications(_ sender: UITapGestureRecognizer) {
print("Really true")
self.notificationsIcon.image = UIImage.init(named: "notifications_tapped")
previousIndex = currentIndex
currentIndex = 2
updateTapView(title: notificationsTitle, icon: notificationsIcon)
previousSeelctedTitle = notificationsTitle
previousSelectedIcon = notificationsIcon
if i > 0 {
removePreviousViewController()
print("I am already removed")
}
let storyBoard = getStoryBoardByIndentifier(identifier: "notifications")
let vc = storyBoard.instantiateViewController(withIdentifier: "NotificationsViewController") as! NotificationsViewController
showSubViewContrller(subViewController: vc)
i += 1
}
//Mark:- Action button for warnings
#IBAction func onClickMyMessages(_ sender: UITapGestureRecognizer) {
print("ha ha..i believe in that")
self.myMessagesIcon.image = UIImage.init(named: "myMessages_tapped")
previousIndex = currentIndex
currentIndex = 3
updateTapView(title: myMessagesTitle , icon: myMessagesIcon)
previousSeelctedTitle = myMessagesTitle
previousSelectedIcon = myMessagesIcon
if i > 0 {
removePreviousViewController()
print("i am alreday removed")
}
let storyBoard = getStoryBoardByIndentifier(identifier: "myMessages")
let vc = storyBoard.instantiateViewController(withIdentifier: "MyMessagesViewController") as! MyMessagesViewController
showSubViewContrller(subViewController: vc)
i += 1
}
}
Looks like you are adding view ineach viewcotroller which you want to show as if a tab bar item.. this might not be the right way to do.. what you can do is make your custom tabbar controller
class SampleViewController: UITabBarController {
#IBOutlet var tabBarView: UIView!
#IBOutlet weak var item1Btn: UIButton!
#IBOutlet weak var item2Btn: UIButton!
#IBOutlet weak var item3Btn: UIButton!
#IBOutlet weak var item4Btn: UIButton!
#IBOutlet weak var item5Btn: UIButton!
let item1Image = #imageLiteral(resourceName: "profileUnselected")
let item1Selected = #imageLiteral(resourceName: "profile")
let item2Image = #imageLiteral(resourceName: "notificationUnselected")
let item2Selected = #imageLiteral(resourceName: "notificationSelected")
let item3Image = #imageLiteral(resourceName: "game")
let item3Selected = #imageLiteral(resourceName: "game")
let item4Image = #imageLiteral(resourceName: "cookbooksUnselected")
let item4Selected = #imageLiteral(resourceName: "cookbooks")
let item5Image = #imageLiteral(resourceName: "searchTabUnselected")
let item5Selected = #imageLiteral(resourceName: "searchTabSelected")
let unSelectedColor = UIColor.lightGray
let selectedColor = UIColor.white
let appColor = UIColor(red: 22.0/255.0, green: 168.0/255.0, blue: 225.0/255.0, alpha: 1.0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if UIDevice().userInterfaceIdiom == .phone {
switch UIScreen.main.nativeBounds.height {
case 2436:
offsetHeight = 44.0
default:
print("unknown")
}
}
addTabView()
}
func addTabView() {
Bundle.main.loadNibNamed("TabBarView", owner: self, options: nil)
tabBarView.frame = CGRect(x:
0 , y: Helper.getScreenHeight() - AppConstants.kTabBarheight - offsetHeight, width: Helper.getScreenWidth(), height: AppConstants.kTabBarheight)
self.view.addSubview(tabBarView)
self.item3Btn.sendActions(for: .touchUpInside) //here i showed my third tab bar item initially
}
#IBAction func tabBarBtnClick(_ sender: UIButton) {
var isViewToChange = false
let currentIndex = self.selectedIndex
var tochangeIndex = sender.tag
self.item1Btn.setImage(item1Image, for: .normal)
self.item2Btn.setImage(item2Image, for: .normal)
self.item3Btn.setImage(item3Image, for: .normal)
self.item4Btn.setImage(item4Image, for: .normal)
self.item5Btn.setImage(item5Image, for: .normal)
switch sender.tag {
case 0:
self.item1Btn.setImage(item1Selected, for: .normal)
if currentIndex != 0{
isViewToChange = true
tochangeIndex = 0
}
else{
(self.viewControllers?[currentIndex] as! UINavigationController).popToRootViewController(animated: true)
}
case 1:
self.item2Btn.setImage(item2Selected, for: .normal)
if currentIndex != 1{
isViewToChange = true
tochangeIndex = 1
}
else{
(self.viewControllers?[currentIndex] as! UINavigationController).popToRootViewController(animated: true)
}
case 2:
if currentIndex != 2{
isViewToChange = true
tochangeIndex = 2
}
else{
(self.viewControllers?[currentIndex] as! UINavigationController).popToRootViewController(animated: true)
}
case 3:
self.item4Btn.setImage(item4Selected, for: .normal)
if currentIndex != 3{
isViewToChange = true
tochangeIndex = 3
}
else{
(self.viewControllers?[currentIndex] as! UINavigationController).popToRootViewController(animated: true)
}
case 4:
self.item5Btn.setImage(item5Selected, for: .normal)
if currentIndex != 4{
isViewToChange = true
tochangeIndex = 4
}
else{
(self.viewControllers?[currentIndex] as! UINavigationController).popToRootViewController(animated: true)
}
default:
break
}
if isViewToChange {
let _ = self.selectedViewController?.view
let _ = self.viewControllers?[tochangeIndex].view
self.selectedIndex = tochangeIndex
}
}
func hideTabbar() {
self.tabBar.isHidden = true
var frame = tabBarView.frame
frame.origin.y = Helper.getScreenHeight() + 200
tabBarView.frame = frame
}
func showTabbar() {
self.tabBar.isHidden = false
var frame = tabBarView.frame
frame.origin.y = Helper.getScreenHeight() - AppConstants.kTabBarheight - offsetHeight
tabBarView.frame = frame
}
}
here i have aded methods for you to either show the tab bar of hide as we push into the stack of each tab bar item..
Here added few images for your understanding
and also dont forgot to set the tag for each button in tab bar view, so that when any button clicked, same method is called and we handle it from there

Programmatically-added page control disappears when setting view controllers after setup

Problem
When tapping the skip button on page i (which calls setViewControllers(_:animated:) and transitions the user to the last page in the page view controller), and then swiping back to page i again, the page control disappears.
Wanted result
I want to programmatically add and show a custom page control on the bottom of each view controller in a page view controller when said page view controller contains different types of view controllers.
Efforts so far to resolve the issue
Adding the page control to the base view controller each time it appears.
Calling loadView() on the view controller that contains the missing page control.
Code
I have a WalkthroughRootViewController that contains a UIPageViewController. The type of the view controllers in the page view controller are two subclasses of type WalkthroughBaseViewController, the first n-1 of one type, and the last of the other. I have not included code of the last type, as that's working as far as I can see.
I have this code in WalkthroughBaseViewController:
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
The page control is added to the view in viewDidLoad():
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
If the user is on any of the first n-1 view controllers, there is a skip button the user can tap to skip forward to the last view controller. The code for this is
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
Reference
I have highlighted the code I believe is important, but here are all files related to the walkthrough of the application.
WalkthroughRootViewController
import UIKit
class WalkthroughRootViewController: UIViewController {
// MARK: Regular Properties
var walkthroughPageViewController: UIPageViewController!
var walkthroughImages = [
Images.w1,
Images.w2
]
var walkthroughStrings: [String] = [
.localized(.walkthroughTitle1),
.localized(.walkthroughZipCodeTitle)
]
// MARK: Lazy Properties
lazy var walkthroughPageViewControllerDataSource: WalkthroughPageViewControllerDataSource = {
var dataSource = WalkthroughPageViewControllerDataSource()
dataSource.walkthroughRootViewController = self
return dataSource
}()
// MARK: Computed Properties
var lastIndex: Int {
return walkthroughImages.count - 1
}
var temporaryUserInput: String?
var temporarySwitchPosition = false
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
// MARK: View Controller Life Cycle
extension WalkthroughRootViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Stop gray shadow from appearing under transition.
navigationController?.view.backgroundColor = .white
configurePageViewController()
}
}
// MARK: Helper Methods
extension WalkthroughRootViewController {
func configurePageViewController() {
walkthroughPageViewController = UIPageViewController(transitionStyle: .scroll, navigationOrientation: .horizontal, options: nil)
walkthroughPageViewController.dataSource = walkthroughPageViewControllerDataSource
walkthroughPageViewController.delegate = walkthroughPageViewControllerDataSource
let startingViewController = storyboard!.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as! WalkthroughImageViewController
let startIndex = 0
startingViewController.delegate = self
startingViewController.pageIndex = startIndex
startingViewController.text = walkthroughStrings[startIndex]
startingViewController.image = walkthroughImages[startIndex]
startingViewController.numberOfPages = walkthroughImages.count
walkthroughPageViewController.setViewControllers([startingViewController], direction: .forward, animated: true)
walkthroughPageViewController.view.frame = view.bounds
add(walkthroughPageViewController)
}
}
extension WalkthroughRootViewController: WalkthroughDelegate {
func skipWalkthrough() {
guard let viewController = walkthroughPageViewControllerDataSource.viewController(at: lastIndex, storyboard: storyboard!) else { return }
walkthroughPageViewController.setViewControllers([viewController], direction: .forward, animated: true)
}
}
extension WalkthroughRootViewController: WalkthrouZipCodeViewControllerDelegate {
func walkththroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userEnteredText enteredText: String) {
temporaryUserInput = enteredText
}
func walkthroughZipCodeViewController(_ viewController: WalkthroughZipCodeViewController, userChangedSwitchPosition position: Bool) {
temporarySwitchPosition = position
}
}
WalkthroughBaseViewController
import UIKit
protocol WalkthroughDelegate: class {
func skipWalkthrough()
}
class WalkthroughBaseViewController: UIViewController {
// MARK: Regular Properties
var pageIndex = 0
var text = ""
var delegate: WalkthroughDelegate?
var numberOfPages = 0
// Lazy Properties
lazy var pageControl: UIPageControl = {
let pageControl = UIPageControl(frame: .zero)
pageControl.translatesAutoresizingMaskIntoConstraints = false
pageControl.numberOfPages = numberOfPages
pageControl.sizeToFit()
pageControl.pageIndicatorTintColor = Colors.brown
pageControl.currentPageIndicatorTintColor = Colors.silver
pageControl.isUserInteractionEnabled = false
pageControl.isEnabled = false
return pageControl
}()
}
// MARK: View Controller Life Cycle
extension WalkthroughBaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = Colors.silver
view.addSubview(pageControl)
NSLayoutConstraint.activate([
pageControl.bottomAnchor.constraint(equalTo: view.bottomAnchor),
pageControl.centerXAnchor.constraint(equalTo: view.centerXAnchor)
])
view.accessibilityIdentifier = Strings.AccessibilityIdentifiers.walkthrough
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
pageControl.currentPage = pageIndex
}
}
WalkthroughImageViewController
import UIKit
class WalkthroughImageViewController: WalkthroughBaseViewController {
// MARK: #IBOutlets
#IBOutlet weak var titleLabel: UILabel! {
didSet {
titleLabel.adjustsFontSizeToFitWidth = true
titleLabel.textColor = Colors.silver
titleLabel.numberOfLines = 0
}
}
#IBOutlet weak var skipWalkthroughButton: UIButton! {
didSet {
skipWalkthroughButton.setTitleColor(Colors.silver, for: .normal)
skipWalkthroughButton.titleLabel?.font = UIFont.preferredBoldFont(for: .body)
skipWalkthroughButton.setTitle(.localized(.skip), for: .normal)
}
}
#IBOutlet weak var imageView: UIImageView! {
didSet {
imageView.layer.shadowColor = Colors.brown.cgColor
imageView.layer.shadowOffset = CGSize(width: 0, height: 1)
imageView.layer.shadowOpacity = 1
imageView.layer.shadowRadius = 1.0
imageView.clipsToBounds = false
imageView.contentMode = .scaleAspectFill
}
}
// MARK: Regular Properties
var image: UIImage?
// MARK: View Controller Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
imageView.image = image
titleLabel.text = text
}
}
// MARK: #IBActions
extension WalkthroughImageViewController {
#IBAction func skipWalkthrough(_ sender: UIButton) {
delegate?.skipWalkthrough()
}
}
WalkthroughPageViewControllerDataSource
import UIKit
class WalkthroughPageViewControllerDataSource: NSObject {
// MARK: Regular Properties
var walkthroughRootViewController: WalkthroughRootViewController!
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDataSource {
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound || index == 0 {
return nil
}
index -= 1
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
var index = indexOfViewController(viewController as! WalkthroughBaseViewController)
if index == NSNotFound {
return nil
}
index += 1
if index == walkthroughRootViewController.walkthroughImages.count {
return nil
}
return self.viewController(at: index, storyboard: walkthroughRootViewController.storyboard!)
}
}
extension WalkthroughPageViewControllerDataSource {
func viewController(at index: Int, storyboard: UIStoryboard) -> WalkthroughBaseViewController? {
if walkthroughRootViewController.walkthroughImages.count == 0 || index >= walkthroughRootViewController.walkthroughImages.count {
return nil
}
var viewController: WalkthroughBaseViewController?
if index == walkthroughRootViewController.lastIndex {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughZipCode) as? WalkthroughZipCodeViewController
if let viewController = viewController as? WalkthroughZipCodeViewController {
viewController.pageIndex = index
viewController.walkthroughZipCodeDelegate = walkthroughRootViewController
viewController.temporaryUserInput = walkthroughRootViewController.temporaryUserInput
viewController.temporarySwitchPosition = walkthroughRootViewController.temporarySwitchPosition
viewController.numberOfPages = walkthroughRootViewController.walkthroughImages.count
viewController.image = walkthroughRootViewController.walkthroughImages[index]
}
} else {
viewController = storyboard.instantiateViewController(withIdentifier: Strings.ViewControllerIdentifiers.walkthroughImage) as? WalkthroughImageViewController
if let viewController = viewController as? WalkthroughImageViewController {
viewController.delegate = walkthroughRootViewController
viewController.pageIndex = index
viewController.image = walkthroughRootViewController.walkthroughImages[index]
viewController.text = walkthroughRootViewController.walkthroughStrings[index]
}
}
return viewController
}
func indexOfViewController(_ viewController: WalkthroughBaseViewController) -> Int {
return viewController.pageIndex
}
}
extension WalkthroughPageViewControllerDataSource: UIPageViewControllerDelegate {
}
Create a single UIPageControl that you put in the WalkthroughRootViewController and update it when you navigate the pages - don't create a page control for each child.
Try not to use extensions to override methods - it can cause you trouble - see this blog entry.

Resources