I have a tableView with a textField within row 0 and textView within row 3. My tableview currently slides up every time when the keyboard is present. When the tableView slides up, you can't see the textField within row 0. How do I disable this for row 0 and just keep for row 3? I tried using using Protocol & Delegates to try to encapsulate the function only for row 3, but that doesn't work.
class CreateEditItemController: UIViewController, CreateItemDescriptionCellDelegate {
#IBOutlet weak var tableView: UITableView!
func handleKeyboardShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y == 0 {
//self.view.frame.origin.y -= keyboardSize.height
self.view.frame.origin.y -= 200
}
}
}
func handleKeyboardHide(notification: NSNotification) {
if self.view.frame.origin.y != 0 {
self.view.frame.origin.y = 0
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
...
case 3:
let cell = tableView.dequeueReusableCell(withIdentifier: "CreateItemDescriptionCell", for: indexPath) as! CreateItemDescriptionCell
cell.delegate = self
return cell
default:
return tableView.cellForRow(at: indexPath)!
}
}
}
protocol CreateItemDescriptionCellDelegate: class {
func handleKeyboardShow(notification: NSNotification)
func handleKeyboardHide(notification: NSNotification)
}
class CreateItemDescriptionCell: UITableViewCell, UITextViewDelegate {
//IBOUTLETS
#IBOutlet weak var notesTextView: UITextView!
weak var delegate: CreateItemDescriptionCellDelegate?
override func awakeFromNib() {
super.awakeFromNib()
notesTextView.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardShow), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardHide), name: UIResponder.keyboardWillHideNotification, object: nil)
}
#objc func handleKeyboardShow(notification: NSNotification) {
delegate?.handleKeyboardShow(notification: notification)
}
#objc func handleKeyboardHide(notification: NSNotification) {
delegate?.handleKeyboardHide(notification: notification)
}
}
What you are trying to do is possible after some mathematics but i would recommend using a third party pod for this instead of doing this manually on evert controller.
Add this to your pod file:
# IQKeyboardManager: Codeless drop-in universal library allows to prevent issues of keyboard sliding up
# https://github.com/hackiftekhar/IQKeyboardManager
pod 'IQKeyboardManagerSwift'
For further detail and documentation view:
https://github.com/hackiftekhar/IQKeyboardManager
The only line you would have to write would be :
// Enabling IQKeyboardManager
IQKeyboardManager.shared.enable = true
in
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
This will solve all your problems and you wont have to calculate the frames or anything.
There are a few routes you can go down, but for simplicities sake, there are a few libraries you can use to get all the calculations down for you and you don't need to worry about it.
One I use all the time is:
TPKeyboardAvoiding - https://github.com/michaeltyson/TPKeyboardAvoiding
IQKeyboardManagerSwift - https://github.com/hackiftekhar/IQKeyboardManager
And many others.
Related
I have a TabelViewController. The data inside the TableViewCells is being updated with a high frequency (lets assume 10 Hz) via the tableview.reloadData(). This works so far. But when I scroll the TableView, the update is paused, until the user interaction ends. Also all other processes inside my app are paused. How can I fix this?
Here is an example TableViewController. If you run this on your emulator and check the output inside the debug area, you will notice, that not only the update of the graphics (here a label) is paused, but also the notifications, when you scroll and hold the tableview. This is also the case, if you have a process in another class.
It's interesting, that this blocking of processes is not the case if you interact with a MapView. What am I missing here?
import UIKit
class TableViewController: UITableViewController {
var text = 0
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(self.recieveNotification), name: NSNotification.Name(rawValue: "testNotificatiion"), object: nil)
scheduledTimerWithTimeInterval()
}
func scheduledTimerWithTimeInterval(){
timer = Timer.scheduledTimer(timeInterval: 1/10, target: self, selector: #selector(self.updateCounting), userInfo: nil, repeats: true)
}
#objc func updateCounting(){
text += 1
let userInfo = ["test": text]
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "testNotificatiion"), object: nil, userInfo: userInfo)
tableView.reloadData()
}
#objc func recieveNotification(notification: Notification){
if let userInfo = notification.userInfo! as? [String: Int]
{
if let recieved = userInfo["test"] {
print("Notification recieved with userInfo: \(recieved)")
}
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell?
cell = tableView.dequeueReusableCell(withIdentifier: "rightDetail", for: indexPath)
cell?.detailTextLabel?.text = String(text)
return cell!
}
}
Timer's don't fire when a UITableView is being scrolled. Check out this answer here by Quinn The Eskimo.
Change your method to be like this and it'll work even while scrolling.
func scheduleMyTimer() {
let t = Timer(timeInterval: 1.0, repeats: true) { _ in
self.updateCounting()
}
RunLoop.current.add(t, forMode: RunLoop.Mode.common)
}
CoreLocation callbacks also aren't firing when a UITableView is being scrolled. Here's a fix for that.
From the documentation for CLLocationManagerDelegate:
Core Location calls the methods of your delegate object on the runloop
from the thread on which you initialized CLLocationManager. That
thread must itself have an active run loop, like the one found in your
app’s main thread.
So I changed the CLLocationManager init from this:
class AppDelegate {
var lm = CLLocationManager()
}
To this:
class AppDelegate {
var lm: CLLocationManager!
var thread: Thread!
func didFinishLaunching() {
thread = Thread {
self.lm = CLLocationManager()
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (_) in
// empty on purpose
}
RunLoop.current.run()
// If no input sources or timers are attached
// to the run loop, the run() method exits immediately,
// so we add a Timer just before the call.
}
thread.start()
}
}
Then the delegate callbacks kept firing even while scrolling. This answer helped.
I have a ViewController which uses multiple Subviews (HomeViewController, etc.) which can be selected via a Custom Tab Bar at the bottom of my app. Inside the HomeViewController there is a UIView containing a UITableView containing a Prototype Custom Cell with name and image.
import UIKit
class HomeViewController: UIViewController {
#IBOutlet weak var friendView: UITableView!
let friends = ["batman", "harsh", "ava", "sasha", "fatima", "alfred"]
override func viewDidLoad() {
super.viewDidLoad()
friendView.delegate = self
friendView.dataSource = self
friendView.allowsSelection = false
}
}
extension HomeViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 120
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friends.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = friendView.dequeueReusableCell(withIdentifier: "customCell") as! CustomCell
let friend = friends[indexPath.row]
cell.avatarImg.image = UIImage(named: friend)
cell.nameLbl.text = friend
return cell
}
}
Custom cell:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var friendView: UIView!
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var avatarImg: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
When I start the app, everything looks just fine. However, when I start scrolling inside the table, all data suddenly disappears. All relations between storyboard and code should be just fine. I think it might have got something to do with my need of using a Subview.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var tabBarView: UIView!
#IBOutlet weak var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
Design.makeCornersRound(view: tabBarView, radius: 10.0)
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { (timer) in
self.switchToHomeViewController()
}
}
#IBAction func onClickTabBar(_ sender: UIButton) {
let tag = sender.tag
if tag == 1 {
switchToIncomingsViewController()
}
else if tag == 2 {
switchToSpendingsViewController()
}
else if tag == 3 {
switchToHomeViewController()
}
else if tag == 4 {
switchToSavingsViewController()
}
else if tag == 5 {
switchToSettingsViewController()
}
}
func switchToHomeViewController() {
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
contentView.addSubview(Home.view)
Home.didMove(toParent: self)
}
...
}
Reference to the tutorial I have been trying to implement: https://www.youtube.com/watch?v=ON3Z0PXSoVk
In this function:
func switchToHomeViewController() {
// 1
guard let Home = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// 2
contentView.addSubview(Home.view)
// 3
Home.didMove(toParent: self)
// 4
}
At 1 you create an instance of HomeViewController
at 2 you add its view to cotentView
at 3 you call didMove() ... but that doesn't do anything because you haven't added the controller to your hierarchy
at 4 your Home instance goes away, so the code in that controller no longer exists
You need to add the controller as a child controller.
As a side note, use lowerCase for variable names:
func switchToHomeViewController() {
// create an instance of HomeViewController
guard let homeVC = self.storyboard?.instantiateViewController(withIdentifier: "HomeViewController") as? HomeViewController else { return }
// add it as a child view controller
self.addChild(homeVC)
// add its view
contentView.addSubview(homeVC.view)
// here you should either set the view's frame or add constraints
// such as:
homeVC.view.frame = contentView.bounds
// inform the controller that it moved to a parent controller
homeVC.didMove(toParent: self)
}
I want to create a custom XIB Table View Cell with an action button that can segue to a new view controller.
So far, I created a custom XIB Cell (TaskListCell) with a button (timeButton), and registered the TaskListCell to TaskListViewController. For tableView(_:cellForRowAt:), I added tag for timeButton and addTarget(_:action:for:) to respond when timeButton is tapped. However, I get this error message: Thread 1: "-[Sprints.TaskListCell pressedTimeButton:]: unrecognized selector sent to instance 0x105311560"
Here is the custom XIB cell file:
class TaskListCell: UITableViewCell {
#IBOutlet weak var nameField: UITextField!
#IBOutlet weak var timeButton: UIButton!
#IBAction func pressedTimeButton(_ sender: UIButton) {
}
override func awakeFromNib() {
}
override func setSelected(_ selected: Bool, animated: Bool) {
}
}
Here is the ViewController that displays the custom cells in a Table View:
class TaskListViewController: UIViewController {
// MARK: - Outlet Variables
#IBOutlet weak var taskList: SelfSizedTableView!
...
// MARK: - Instance Variables
var taskData = [TaskData]()
var taskCount: Int = 1
...
// MARK: - View Controller Methods
override func viewDidLoad() {
super.viewDidLoad()
// Register TaskListCell.xib file
let taskCellNib = UINib(nibName: "TaskCell", bundle: nil)
taskList.register(taskCellNib, forCellReuseIdentifier: "taskCell")
...
// Connect table view's dataSource and delegate to current view controller
taskList.delegate = self
taskList.dataSource = self
}
// MARK: - Action Methods
// Adds new cell in Table View
#IBAction func pressedAddTask(_ sender: UIButton) {
taskCount += 1
taskList.reloadData()
taskList.scrollToRow(at: IndexPath(row: taskCount-1, section: 0), at: .bottom, animated: true)
}
// MARK: - Methods
// Segues to SelectTime screen when timeButton is pressed
#objc func pressedTimeButton(_ sender: UIButton) {
let destination = SelectTimeViewController()
navigationController?.pushViewController(destination, animated: true)
}
}
extension TaskListViewController: UITableViewDataSource {
// Return the number of rows in table view
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return taskCount
}
// Return the cell to insert in table view
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "taskCell", for: indexPath) as! TaskListCell
// Fatal error for next line: "Unexpectedly found nil while implicitly unwrapping an Optional value"
cell.timeButton.tag = indexPath.row
cell.timeButton.addTarget(self, action: #selector(pressedTimeButton(_:)), for: .touchUpInside)
return cell
}
}
extension TaskListViewController: UITableViewDelegate {
}
Check instance of IBAction from your cell may be it is being called. Remove this function and it will work
#IBAction func pressedTimeButton(_ sender: UIButton) {
}
Stackoverflow
I know how to make a button in the table view cells with website links, rate, mail, and many things. However, How could I open the view controller with the instantiateViewController in the #Objc func's statements?
For example.
Create a new Table View Cell folder called FeedBackButtonsTableViewCell
class FeedBackButtonsTableViewCell: UITableViewCell {
#IBOutlet weak var ButtonCells: UIButton!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Let create a new view controller folder called
class FeedbackViewController: UIViewController {
#IBOutlet weak var TableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
}
}
add the extension to calling the view controller to UITableViewDataSource and UITableViewDelegate and create a obj func statements inside of the second FeedbackViewController with UITableViewDataSource and UITableViewDelegate under the cells.
extension FeedbackViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
if indexPath.row == 1 {
buttonCell = TableView.dequeueReusableCell(withIdentifier: "ButtonCells") as? FeedBackButtonsTableViewCell
buttonCell?.ButtonCells.addTarget(self,action: #selector(LearnMore),for: .touchUpInside)
buttonCell?.ButtonCells.tag = indexPath.row
return buttonCell!
}
#objc func LearnMore() {
// How could I write to open the view controller with UIButton in the Table View Cells?
}
}
Thank you for bring a kind of help! :)
Simple solution could be to use procol.
protocol CellActionDelegate{
func didButtonTapped(index: Int)
}
Now confirm the protocol in FeedbackViewController. Take index and actionDelegate properties in your UITableViewCell subclass.
class FeedBackButtonsTableViewCell: UITableViewCell{
var actionDelegate: CellActionDelegate?
var index: Int?
.....
// Take Action of UIButton here
#IBAction func more(_ sender: Any) {
if let delegate = self.actionDelegate{
delegate.didButtonTapped(index!)
}
}
}
Now in your FeedbackViewController set actionDelegate & Corresponding index in
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {}
you can open anotherView controller from func didButtonTapped(index: Int) definition .
extension FeedbackViewController:CellActionDelegate{
func didButtonTapped(index: Int) {
let storybord = UIStoryboard(name: "Main", bundle: nil)
guard let controller = storybord.instantiateViewController(withIdentifier: "AnotherControllerIdentfier") as? AnotherViewController else{
fatalError("Could not finc another view controller")
}
self.present(controller, animated: true, completion: nil)
}
}
#objc func LearnMore() {
let viewController = FeedbackDetailsViewController()// creation of viewController object differs depends on how you fetch the UI, means either you are using storyboard or xib or directly making ui in code.
self.navigationController?.pushViewController(viewController, animated: true)
}
I have the following extension, which hides the keyboard when ever a tap is registered anywhere in the view.
//Extension to hide the keyboard when tap anywhere
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
This used in the viewDidLoad() of my ViewController which also is a delegate/datasource to a TableView Controller.
self.hideKeyboardWhenTappedAround()
The dismissal of the keyboard works perfectly well, although the behaviour that I am after is for the keyboard to be dismissed first at first tap anywhere on the view/tableview before the user taps again to select a row from the search results in a tableview.
Currently a tap anywhere is not only dismissing the keyboard but also selecting the cell where the user has tapped.
Add notification observer and Check for keyboard in didselectrow method.
var isKeyBoard = false
override func viewDidLoad() {
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillDisappear(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillAppear(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
}
#objc func keyboardWillAppear(_ notification: NSNotification) {
if let userInfo = notification.userInfo,
isKeyBoard = true
}
}
#objc func keyboardWillDisappear(_ notification: NSNotification) {
isKeyBoard = false
}
deinit {
NotificationCenter.default.removeObserver(self)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if isKeyBoard {
self.view.endEditing(true)
return
}
}
Just a Normal check you can initialise as
Keyboard is Open That means A TextField Is acting as a First Responder
Now you just simply need to check in didSelect Method here I am Showing you in my collectionView DidSelect Method same as you can do in your Tableview DidSelect
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath)
{
/// Check do TF is First Responder
/// If yes Just Hide the Keyboard for first time
if propertySearchTF.isFirstResponder {
self.view.endEditing(true)
return
}
/// if TF is not First Responder
/// Execution will start from here
/// DidSelect Code
}
Tested in TableView :
TF Outlet
#IBOutlet weak var myTF: UITextField!{
didSet{
myTF.becomeFirstResponder()
}
}
TableView Method
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if self.myTF.isFirstResponder {
self.view.endEditing(true)
return
}
print(selectedKey)
selectedimage = images[indexPath.row] as UIImage
performSegue(withIdentifier: "segue", sender: self)
}
You have to add the gesture on tableView with cancelsTouchesInView = true
extension UITableView {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
tap.cancelsTouchesInView = true
self.addGestureRecognizer(tap)
}
#objc func dismissKeyboard() {
self.endEditing(true)
}
}
Call from viewDidLoad() tableView.hideKeyboardWhenTappedAround()