Why does the view disappear after scrolling through the collection view? - swift

I am trying to create a view which slides up when the keyboard appears, and within this view is a text field functioning as a search bar and collection view to hold the results of the search. The View slides up fine right above the keyboard as it is supposed to however once I tap on one of the cells to scroll horrizontally the view just disappears. What could be causing this.
Code That I Think May Be Causing The Problem
var offsetY:CGFloat = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: Int(animationCurveRawValue))
if let _ = endFrame, endFrame!.intersects(self.myView.frame) {
self.offsetY = self.myView.frame.maxY - endFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.myView.frame.origin.y = self.myView.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.myView.frame.origin.y = self.myView.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}
Code in its entirety
import UIKit
class SearchCollectionViewController: UIViewController,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout, UITextFieldDelegate, UICollectionViewDataSource {
#IBOutlet weak var searchBar: UITextField!
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var myView: UIView!
var genericArray:[String] = ["A","B","C","D","E","F","G","Ab","Abc"]
var currentGenericArray:[String] = [String]()
override func viewDidLoad() {
super.viewDidLoad()
collectionView.delegate = self
collectionView.dataSource = self
let layout: UICollectionViewFlowLayout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 0, left: 0, bottom: 10, right: 0)
layout.itemSize = CGSize(width: (UIScreen.main.bounds.width-1)/2, height: (UIScreen.main.bounds.width-1)/2)
layout.minimumInteritemSpacing = 1
layout.minimumLineSpacing = 1
layout.scrollDirection = .horizontal
self.collectionView.collectionViewLayout = layout
searchBar.delegate = self
currentGenericArray = genericArray
searchBar.addTarget(self, action: #selector(TableSearchViewController.textFieldDidChange), for: .editingChanged)
}
#objc func textFieldDidChange(){
guard(!(searchBar.text?.isEmpty)!) else{
currentGenericArray = genericArray
collectionView.reloadData()
return
}
currentGenericArray = genericArray.filter({letter -> Bool in
letter.lowercased().contains(searchBar.text!)
})
collectionView.reloadData()
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return currentGenericArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as! CollectionCell
cell.collectionLabel.text = currentGenericArray[indexPath.row]
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
var offsetY:CGFloat = 0
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.keyboardFrameChangeNotification(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil)
}
#objc func keyboardFrameChangeNotification(notification: Notification) {
if let userInfo = notification.userInfo {
let endFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect
let animationDuration = userInfo[UIKeyboardAnimationDurationUserInfoKey] as? Double ?? 0
let animationCurveRawValue = (userInfo[UIKeyboardAnimationCurveUserInfoKey] as? Int) ?? Int(UIViewAnimationOptions.curveEaseInOut.rawValue)
let animationCurve = UIViewAnimationOptions(rawValue: UInt(animationCurveRawValue))
if let _ = endFrame, endFrame!.intersects(self.myView.frame) {
self.offsetY = self.myView.frame.maxY - endFrame!.minY
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.myView.frame.origin.y = self.myView.frame.origin.y - self.offsetY
}, completion: nil)
} else {
if self.offsetY != 0 {
UIView.animate(withDuration: animationDuration, delay: TimeInterval(0), options: animationCurve, animations: {
self.myView.frame.origin.y = self.myView.frame.origin.y + self.offsetY
self.offsetY = 0
}, completion: nil)
}
}
}
}
}
class CollectionCell:UICollectionViewCell{
#IBOutlet weak var collectionLabel: UILabel!
override func awakeFromNib() {
collectionLabel.textAlignment = .center
}
}

Your code is a bit muddled but I don't see any textField functions except for didChange which you call from your searchBar. Try adding a shouldReturn and a didEndEditing, as well as update your slider. Here is an example of what I would do. Try it and see if it makes a difference.
Lifecycle
let keyboardSlider = KeyboardSlider()
override func viewDidLoad() {
super.viewDidLoad()
keyboardSlider.subscribeToKeyboardNotifications(view: view)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
keyboardSlider.unsubscribeFromKeyboardNotifications()
}
TextField
// MARK: TextFieldDelegate
func textFieldDidBeginEditing(_ textField: UITextField) {
// Add Done button for Keyboard Dismissal
let toolBar = UIToolbar()
toolBar.sizeToFit()
toolBar.barStyle = .default
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.didStopEditing))
let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
toolBar.setItems([space, doneButton], animated: false)
textField.inputAccessoryView = toolBar
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
/// Helper to dismiss keyboard
#objc func didStopEditing() {
textFieldShouldReturn(phoneNumberTextField)
}
func textFieldDidEndEditing(_ textField: UITextField) {
UIView.setAnimationCurve(UIViewAnimationCurve.easeInOut)
UIView.animate(withDuration: 0.2) {
self.view.frame.origin.y = 0
}
}
Keyboard Slider
class KeyboardSlider: NSObject {
// variables to hold and process information from the view using this class
weak var view: UIView?
#objc func keyboardWillShow(notification: NSNotification) {
// method to move keyboard up
view?.frame.origin.y = 0 - getKeyboardHeight(notification as Notification)
}
func getKeyboardHeight(_ notification:Notification) -> CGFloat {
// get exact height of keyboard on all devices and convert to float value to return for use
let userInfo = notification.userInfo
let keyboardSize = userInfo![UIKeyboardFrameEndUserInfoKey] as! NSValue
return keyboardSize.cgRectValue.height
}
func subscribeToKeyboardNotifications(view: UIView) {
// assigning view to class' counterpart
self.view = view
// when UIKeyboardWillShow do keyboardWillShow function
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: .UIKeyboardWillShow, object: nil)
}
func unsubscribeFromKeyboardNotifications() {
NotificationCenter.default.removeObserver(self, name: .UIKeyboardWillShow, object: nil)
}
}
This answer is mainly to clean up your code
I have a feeling that your problem is in your didChange method on keyboard dismissal. Cleaning up your code and stepping through breakpoints will help locate your issue.

Related

App displays text with different characters swift

I am making a chat app. When i send my messages or receive from other user they end up being displayed like this. What might be the issue? It works alright but sometimes it just changes the way the texts are displayed. Am not sure what am missing in this. Can anyone take a look at it. Kindly. Thanks in advance
Below is my code
class ChatController: UIViewController,UITextViewDelegate,UITableViewDataSource,UITableViewDelegate,UIGestureRecognizerDelegate{
#IBOutlet weak var txtViewBottomConstraints: NSLayoutConstraint!
#IBOutlet weak var viewTextViewContainer: ViewCustom!
#IBOutlet weak var txtViewContainerHeightConstraints: NSLayoutConstraint!
#IBOutlet weak var txtViewHeightConstraints: NSLayoutConstraint!
#IBOutlet var lblUserName: UILabel!
#IBOutlet var userImg: UIImageView!
#IBOutlet weak var txtView: IQTextView!
#IBOutlet weak var tblViewChat: UITableView!
#IBOutlet weak var bottomViewBottomConstraints: NSLayoutConstraint!
#IBOutlet weak var btnSend: UIButton!
var grpId = String()
var getMessageTimer: Timer!
var scrollEnable : Bool = false
var imagePicker : UIImagePickerController? = nil
var imageData : Data?
var groupName = String()
var groupImage = String()
var isFromNotification = Bool()
var strId = String()
var objChatVM = ChatViewModel()
var getMessageId = String()
var userImage:URL? = nil
var userName = String()
override func viewDidLoad() {
super.viewDidLoad()
popWithSwipe()
txtView.autocorrectionType = .no
lblUserName.text = userName
/* if userImage != nil
{
userImg.kf.setImage(with:userImage)
}
else
{
userImg.image = UIImage(named: "user")
}*/
userImg.kf.setImage(with:userImage, completionHandler: {
(image, error, cacheType, imageUrl) in
if image != nil{
self.userImg.image = image
}
else{
self.userImg.image = #imageLiteral(resourceName: "user")
}
})
IQKeyboardManager.shared.enable = false
IQKeyboardManager.shared.enableAutoToolbar = false
tblViewChat.dataSource = self
tblViewChat.delegate = self
tblViewChat.estimatedRowHeight = 70.0
tblViewChat.rowHeight = UITableViewAutomaticDimension
txtView.delegate = self
// txtView.textContainerInset = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 2)
let tapGestuer = UITapGestureRecognizer(target: self, action: #selector(handleTap(sender:)))
view.addGestureRecognizer(tapGestuer)
tapGestuer.delegate = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
if getMessageTimer != nil{
getMessageTimer.invalidate()
}
getMessageTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(getMessageApi), userInfo: nil, repeats: true)
IQKeyboardManager.shared.enable = false
IQKeyboardManager.shared.enableAutoToolbar = false
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillDisappear(_ animated: Bool) {
getMessageTimer.invalidate()
NotificationCenter.default.removeObserver(self)
}
// MARK:- Get messages from server
#objc func getMessageApi(){
objChatVM.getMessage(param:strId) {status in
if status{
self.tblViewChat.reloadData()
if(self.objChatVM.getNumberOfMessage() != 0){
self.tblViewChat.scrollToRow(at: IndexPath(item: self.objChatVM.getNumberOfMessage()-1, section: 0), at: .bottom, animated: false)
}
}
}
}
#objc func handleTap(sender: UITapGestureRecognizer) {
txtView.resignFirstResponder()
}
// Enable IQKEYBoard manager here for handle keyboard at other controller which has disabled in viewdidload or viewwillappear
override func viewDidDisappear(_ animated: Bool) {
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.enableAutoToolbar = true
}
// MARK:- Gesutrue Delegate Methods
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
// Handle here tap on table view and inside cell for dismiss keyboard while tap outside on the screen
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if (touch.view is SenderTblCell || touch.view is ReceiverTblCell ) {
return false
}
if (touch.view?.superview is SenderTblCell || touch.view?.superview is ReceiverTblCell) {
return false
}
if (touch.view?.superview?.superview is SenderTblCell || touch.view?.superview?.superview is ReceiverTblCell) {
return false
}
if (touch.view?.superview?.superview?.superview is SenderTblCell || touch.view?.superview?.superview?.superview is ReceiverTblCell) {
return false
}
if(touch.view?.superview?.isDescendant(of: SenderTblCell().contentView))! || (touch.view?.superview?.isDescendant(of: ReceiverTblCell().contentView))!{
return false
}
return true // handle the touch
}
// MARK:- KeyBoard will show
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
var safeArea = 0
if UIDevice().userInterfaceIdiom == .phone || UIDevice().userInterfaceIdiom == .pad{
switch UIScreen.main.nativeBounds.height {
case 2436:
bottomViewBottomConstraints.constant = -keyboardSize.height + 30
self.view.layoutIfNeeded()
default:
if #available(iOS 11.0, *) {
let window = UIApplication.shared.keyWindow
safeArea = Int(window?.safeAreaInsets.bottom ?? 0.0)
}
bottomViewBottomConstraints.constant = -keyboardSize.height + CGFloat(safeArea) - 10
self.view.layoutIfNeeded()
}
}
}
}
// MARK:- KeyBoard will hide
#objc func keyboardWillHide(notification: NSNotification) {
bottomViewBottomConstraints.constant = -30
self.view.layoutIfNeeded()
}
#IBAction func btnSendAction(_ sender: Any) {
let param = ["userId":strId,"message":txtView.text!]
objChatVM.sendMessage(param: param) { (status) in
self.txtView.text = ""
self.textViewDidChange(self.txtView)
}
}
//MARK:- TextView Delegate Methods
func textViewDidChange(_ textView: UITextView) {
if textView.text == ""{
//textView.translatesAutoresizingMaskIntoConstraints = true
// txtViewHeightConstraints.constant = 100.0
// btnSend.setImage(#imageLiteral(resourceName: "attachment"), for: .normal)
}else{
// btnSend.setImage(#imageLiteral(resourceName: "sendMsg"), for: .normal)
}
var frame : CGRect = textView.bounds
frame.size.height = textView.contentSize.height
print(frame)
if(frame.height >= 100.0){
textView.isScrollEnabled = true
}
else{
textView.isScrollEnabled = false
txtView.frame.size = frame.size
}
if textView.text == ""{
txtViewContainerHeightConstraints.constant = 50.0
txtViewBottomConstraints.constant = 5.0
txtView.updateConstraints()
viewTextViewContainer.updateConstraintsIfNeeded()
viewTextViewContainer.updateConstraints()
viewTextViewContainer.layoutIfNeeded()
self.view.layoutIfNeeded()
}
}
func textViewDidEndEditing(_ textView: UITextView) {
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
return true
}
// MARK:- TableView DataSource and Delegate Methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objChatVM.getNumberOfMessage()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let messageUserId = objChatVM.getMessageUserFromId(index: indexPath.row)
print(UserViewModel.Shared().getUserId())
if(messageUserId == UserViewModel.Shared().getUserId()){
let cell = tblViewChat.dequeueReusableCell(withIdentifier: "senderCell") as! SenderTblCell
cell.lblMessage.text = objChatVM.getMessage(index: indexPath.row)
cell.lblDate.text = objChatVM.getDateTime(index: indexPath.row)
return cell
}
let cell = tblViewChat.dequeueReusableCell(withIdentifier: "receiverCell") as! ReceiverTblCell
cell.lblMessage.text = objChatVM.getMessage(index: indexPath.row)
cell.lblDate.text = objChatVM.getDateTime(index: indexPath.row)
cell.lblName.text = objChatVM.getFullNameOfUserFrom(index: indexPath.row)
let url = URL(string:objChatVM.getUserFromImage(index:indexPath.row))
cell.imgView.kf.indicatorType = .activity
cell.imgView.kf.setImage(with:url)
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
txtView.resignFirstResponder()
}
// MARK: Side Menu Button Action
#IBAction func btnSideMenuActn(_ sender: UIButton) {
self.pushViewControl(ViewControl:"SideMenuController")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
This is how the message from my server in my logs looks like
message = "Am+good.+How+are+you+my+student%3F";
Use removingPercentEncoding and some of the text such as + doesn't seem to generated by urlEncoding if they are created by code then use both in combination:
message = "Am+good.+How+are+you+my+student%3F"
let decodedMessage = message.removingPercentEncoding?.replacingOccurrences(of: "+", with: " ")
print(decodedMessage)

Read More and Read Less in UITextView : Ilya Puchka readMoreTextView not working

I have tested Ilya Puchka's readMoreTextView pod and on static text inside the project it works. but I am trying it with text that comes from a back end api.
Issue : the cell will not expand fully to reveal all the text. It will reveal about half of the height of the next line...meaning, if the text is 14 pix high, then the READ MORE option will only display only the top half of the text..
If I disable readmoretext options, and use the textView as a plain textView, then textview will display the entire text,... until I scroll.
when I scroll, I lose about 12 lines of text in one messages.
Is a textView just bad for lone singe strings rom a backend?
so this tells me its a tableView.reloadData() or a GCD issue. But I am lost, I don't know where to try. test either option.
I am mixing two pods, AzureBot and ReadMoreTextView. the string / text comes from the azure bot and then gets loaded into the textview.
I now realize I suck at such a simple task.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let message = self.messages[indexPath.row]
let sending = message.from?.id == BotClient.shared.currentUser.id
switch sending {
case true:
let cell = tableView.dequeueReusableCell(withIdentifier: "UserTableViewCell", for: indexPath) as! UserTableViewCell
cell.transform = tableView.transform
cell.messageLabel.text = message.text
return cell
case false:
let cell = tableView.dequeueReusableCell(withIdentifier: "BotTableCell", for: indexPath) as! BotTableCell
cell.botMessage.text = message.text
cell.botMessage.shouldTrim = !expandedCells.contains(indexPath.row)
cell.botMessage.setNeedsUpdateTrim()
cell.botMessage.layoutIfNeeded()
cell.transform = tableView.transform
return cell
}
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "BotTableCell", for: indexPath) as! BotTableCell
cell.botMessage.onSizeChange = { [unowned tableView, unowned self] r in
let point = tableView.convert(r.bounds.origin, from: r)
guard let indexPath = tableView.indexPathForRow(at: point) else { return }
if r.shouldTrim {
self.expandedCells.remove(indexPath.row)
print("REMOVE CALLED")
} else {
self.expandedCells.insert(indexPath.row)
print("INSERT CALLED")
}
tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: "BotTableCell", for: indexPath) as! BotTableCell
cell.botMessage.shouldTrim = !cell.botMessage.shouldTrim
print("SHOULD TRIM CALLED")
cell.backgroundColor = .clear
}
}
Here is my TableViewController Class :
class TMNEMessageViewController: UITableViewController, UITextViewDelegate {
#IBOutlet weak var navBar: UINavigationItem!
private var observer: AnyObject?
private var needsUpdateViewOnAppearance = true
#IBOutlet var messageBar: MessageBar!
public override var inputAccessoryView: UIView? {return messageBar}
public override var canBecomeFirstResponder: Bool {return true}
private var isVisible: Bool {return isViewLoaded && view.window != nil}
var messages: SortedArray<Activity> { return BotClient.shared.messages }
var expandedCells = Set<Int>()
let readMoreTextAttributes: [NSAttributedString.Key: Any] = [
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 16)
]
let readLessTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.red,
NSAttributedString.Key.font: UIFont.italicSystemFont(ofSize: 16)
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 300
tableView.rowHeight = UITableView.automaticDimension
registerCells()
setupObservers()
tableView.transform = CGAffineTransform(a: 1, b: 0, c: 0, d: -1, tx: 0, ty: 0) //flip tableview
BotClient.shared.start { r in
if let _ = r.resource {
print(r.printResponseData())
} else if let error = r.error {
print("ERROR: " + error.localizedDescription)
self.displayError(error.localizedDescription)
}
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if needsUpdateViewOnAppearance {
tableView.reloadData()
needsUpdateViewOnAppearance = false
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
becomeFirstResponder()
}
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if tableView.contentInset == UIEdgeInsets.zero {
updateContentInset(inset: getContentInset())
}
// tableView.reloadData()
}
func registerCells() {
self.tableView.register(UINib(nibName: "BotTableCell", bundle: nil), forCellReuseIdentifier: "BotTableCell")
self.tableView.register(UINib(nibName: "UserTableViewCell", bundle: nil), forCellReuseIdentifier: "UserTableViewCell")
}
fileprivate func setupObservers() {
// Keyboard Notifications
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardDidShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardDidHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification(notification:)), name: UIResponder.keyboardDidChangeFrameNotification, object: nil)
// BotClient Notifications
NotificationCenter.default.addObserver(self, selector: #selector(handleMessageAdded(notification:)), name: .BotClientDidAddMessageNotification, object: BotClient.shared)
}
#objc
func handleKeyboardNotification(notification: Notification) {
switch notification.name {
case UIResponder.keyboardDidChangeFrameNotification:
if let rect = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
DispatchQueue.main.async {
//print("r \(rect) i \(self.tableView.safeAreaInsets)")
// print(self.tableView.contentInset)
let inset = self.tableView.contentInset
self.updateContentInset(inset: UIEdgeInsets(top: (rect.height + 10) - self.tableView.safeAreaInsets.top, left: inset.left, bottom: inset.bottom, right: inset.right))
}
}
default: return
}
}
func updateContentInset(inset: UIEdgeInsets) {
UIView.animate(withDuration: 0.15, animations: {
self.tableView.contentInset = inset
}, completion: { f in
if self.messages.count > 0 {
self.tableView.scrollToRow(at: IndexPath.init(row: 0, section: 0), at: .top, animated: true)
}
})
}
#objc func handleMessageAdded(notification: Notification) {
DispatchQueue.main.async {
if self.isVisible {
self.tableView.reloadData()
} else {
self.needsUpdateViewOnAppearance = true
}
}
}
func getContentInset() -> UIEdgeInsets {
return UIEdgeInsets(top: max(tableView.safeAreaInsets.top, messageBar.frame.height) - tableView.adjustedContentInset.top, left: 0, bottom: tableView.safeAreaInsets.bottom - tableView.adjustedContentInset.bottom, right: 0)
}
// Err
func displayError(_ message: String?) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}

infinite loop when selected programmatically a cell

I have a tableview with a textfield in every row.
I need to reload the tableview and programmatically select the row the user had selected.
The user can write what he wants. The data will be deleted when the textfield's editing has ended and added when the textfield has begun editing.
But I get a infinite loop. Please cloud you help me?
My code :
import UIKit
class OptionsItemViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var textFiedlDelegate: UITextField? = nil
var categorySelected: Category?
var options: [String] = []
var nameOptions: [String] = []
var cellSelected: Int = 0
var viewHeight: CGFloat = 0
var selectedRow: IndexPath? = nil
var tableviewNeedToReload: Bool = false
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var keyboardAlwaysShow: UITextField!
#IBOutlet weak var newFeatureButton: UIBarButtonItem!
private let db = DataBase()
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.dataSource = self
self.textFiedlDelegate?.delegate = self
self.title = categorySelected!.name
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
db.getItemOptions(predicateFormat: "id == \(self.categorySelected!.id)", completion: { results in
self.categorySelected = results.first!
self.options = self.categorySelected!.options as! [String]
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(false)
self.viewHeight = self.view.frame.size.height
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(false)
var index = 0
while index < self.options.count {
if self.options[index] != "" {
index += 1
} else {
self.options.remove(at: index)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
#IBAction func newFeature(_ sender: Any) {
if self.options.last != "" {
let indexPath: IndexPath = IndexPath(row: self.options.count, section: 0)
self.options.append("")
self.tableView.reloadData()
let cell = tableView(self.tableView, cellForRowAt: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
}
// MARK: - TableView Functions
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let option = options[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CellItemOptions.identifier, for: indexPath) as! CellItemOptions
cell.nameOptionsItem.delegate = self
cell.configureCell(with: option)
return cell
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.cellSelected = options.index(of: textField.text!)!
let indexPath: IndexPath = IndexPath(row: self.cellSelected, section: 0)
self.tableView.reloadData()
let cell = self.tableView.cellForRow(at: indexPath) as! CellItemOptions
cell.nameOptionsItem.becomeFirstResponder()
}
func textFieldDidEndEditing(_ textField: UITextField) {
if textField.text! == "" {
if self.options[cellSelected] != "" {
db.setRemoveDetailsItem(category: self.categorySelected!, index: cellSelected)
}
self.options.remove(at: cellSelected)
} else {
self.options[cellSelected] = "\(textField.text!)"
db.setAddDetailsItem(category: self.categorySelected!, index: cellSelected)
}
db.setCategoryOptions(category: self.categorySelected!, options: self.options, index: cellSelected)
}
// MARK: - Keyboard
func keyboardWillShow(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.size.height == self.viewHeight {
self.view.frame.size.height -= keyboardSize.height
}
}
}
func keyboardWillHide(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue {
if self.view.frame.origin.y != self.viewHeight {
self.view.frame.size.height += keyboardSize.height
}
}
}
}
class CellItemOptions: UITableViewCell {
static let identifier = "OptionsItemCell"
#IBOutlet weak var nameOptionsItem: UITextField!
private let tableView = OptionsItemViewController()
func configureCell(with cell: String) {
nameOptionsItem.text = cell
}
}
EDIT :
The loop is due to the reload data...
Like I reload data in textFieldDidBeginEditing(), the view is reloaded more and more ... And I need to textFieldDidBeginEditing() to know the row selected by the user.

UITableViewCell with Custom View

i am desperately trying to add a custom View to a UITableViewCell. I have a UITableViewController in Storyboard wich is linked to a TestTableViewController Class and the prototype cell has the identifier "Cell2".
So this is the code in UITableViewController:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return dataArray.count
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 190
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell2", forIndexPath: indexPath)
let data = dataArray[indexPath.row]
let ticket = YellowTackleTicketComplete()
ticket.frame = CGRect(x: 20, y: 20, width: 335, height: 150)
cell.addSubview(ticket)
// cell.textLabel!.text = tackl!.valueForKey("title") as? String
cell.backgroundColor = UIColor.clearColor()
print(cell.subviews)
return cell
}
The dataArray is just for testing purposes and has one item in it. So what i get is a Table View with one empty cell. In the Xcode UI Debugger i can see the custom view. But its not shown in the simulator or on a device.
If anyone can help, thank you so much!
This is the code of the view:
class YellowTackleTicketComplete: UIView {
var containerView: UIView!
var ticket: TacklTicket!
var dropDownMenu: YellowDrawer!
var dropDownBackground: YellowDrawerBackground!
var ticketShadowLine: UIView!
var lowAlphaView: UIView!
var outbackButton: UIButton!
var arrowView: UIView!
var bounceHeight: CGFloat?
var dropdownHeight: CGFloat?
var topBorder: CGFloat?
var bottomBorder: CGFloat?
//let arrowLayer = CALayer()
//let layerDelegate = LayerDelegate()
var dropDownElements = [String]()
var dropped = false
var animating = false
var delegate: YellowTackleTicketCompleteDelegate?
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addObserver(self, forKeyPath: "highlighted", options: NSKeyValueObservingOptions.New, context: nil)
}
override init(frame: CGRect) {
super.init(frame: frame)
self.addObserver(self, forKeyPath: "highlighted", options: NSKeyValueObservingOptions.New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
self.setNeedsDisplay()
if keyPath == "frame" {
// Set up DropdownMenu
self.dropDownBackground.frame.size.height = self.dropDownMenu.frame.maxY
}
}
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
if(!self.clipsToBounds && !self.hidden && self.alpha > 0.0){
let subviews = self.subviews.reverse()
for member in subviews {
let subPoint = member.convertPoint(point, fromView: self)
if let result:UIView = member.hitTest(subPoint, withEvent:event) {
return result;
}
}
}
return nil
}
func ticketTapped() {
//if !animating {
if !dropped {
showDropdown()
NSLog("#")
// NSLog("Scrolling to \((dropDownMenu.indexPathForSelectedRow?.row)!)(the index of the tableview selection)")
// dropDownMenu.scrollToRowAtIndexPath((dropDownMenu.indexPathForSelectedRow)!, atScrollPosition: .Middle, animated: false)
} else {
hideDropdown()
}
// }
}
func showDropdown() {
dropDownMenu.hidden = false
self.ticket.drawShadowBeneathTicket()
print("showing")
if !animating {
animating = true
UIView.animateWithDuration(
1.0,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: [],
animations: {
self.dropDownMenu.frame.origin.y = self.bottomBorder!
}, completion: { _ in
//self.delegate?.menuOpen?()
self.animating = false
print(self.dropDownBackground.frame.size.height)
print(self.dropDownMenu.frame.maxY)
}
)
}
self.setNeedsDisplay()
self.dropDownMenu!.setNeedsDisplay()
dropped = true
}
func hideDropdown() {
if !animating {
animating = true
UIView.animateWithDuration(
0.7,
delay: 0,
usingSpringWithDamping: 0.7,
initialSpringVelocity: 0.5,
options: [],
animations: {
self.dropDownMenu.frame.origin.y = self.topBorder! + self.bounceHeight!
}, completion: nil
)
}
UIView.animateWithDuration(
0.5,
delay: 0,
options: UIViewAnimationOptions.TransitionNone,
animations: {
self.dropDownMenu.frame.origin.y = self.topBorder! - self.dropdownHeight!
}, completion: { _ in
//self.delegate?.menuClosed?()
self.animating = false
self.dropDownMenu.hidden = true
self.ticket.setNeedsDisplay()
self.setNeedsDisplay()
}
)
dropped = false
}
func initSubViews() {
self.topBorder = self.frame.height*6/150
self.bottomBorder = self.frame.height - self.topBorder!
self.bounceHeight = 250
self.dropdownHeight = 350
containerView = UIView()
containerView.frame = CGRect(x: 0, y: topBorder!, width: self.bounds.width, height: dropdownHeight!+bounceHeight!)
containerView.clipsToBounds = true
ticket = TacklTicket()
ticket.frame = self.bounds
ticket.addTarget(self, action: #selector(YellowTackleTicketComplete.ticketTapped), forControlEvents: .TouchDown)
dropDownMenu = YellowDrawer()
dropDownMenu.frame = CGRect(x: 0, y: topBorder! - dropdownHeight!, width: containerView.bounds.width, height: dropdownHeight!)
dropDownMenu.hidden = true
dropDownMenu.backgroundColor = UIColor.clearColor()
dropDownMenu.addObserver(self, forKeyPath: "frame", options: .New, context: nil)
dropDownBackground = YellowDrawerBackground()
dropDownBackground.frame = CGRectMake(0,0,self.bounds.width,self.dropDownMenu.frame.maxY)
self.addSubview(containerView)
containerView.addSubview(dropDownBackground)
containerView.addSubview(dropDownMenu)
self.addSubview(ticket)
}
}
You should use a designated initializer for your UIView subview. That is either init(frame:CGRect) or in add it in Interface Builder. You are also not passing let data = dataArray[indexPath.row] to your cell, not sure if you're aware of that. You also never call initSubViews() which seems to be a big part of your custom view.
instead of
cell.addSubview(ticket)
try
cell.contentView.addSubview(ticket)
and the best place to customize cell's content is its init function.
Couple of things you should try, set translatesAutoresizingMaskIntoConstraints to false on the view you created programmatically since you are inserting it into an auto layout controller.
View that should also try just setting,
dropDownMenu, dropDownBackground, containerView and ticket
or
In your cellForRowAtIndexPath also try setting the property
ticket.translatesAutoresizingMaskIntoConstraints = false

Moving content behind keyboard doesn't work, Swift

In view controller I have UIImageView and multiple uitextfields. I'm using the following code, based on Apple documentation, to move the scrollView up when a keyboard notification is called, to push up the hidden text fields. However the when the keyboard appears the view does move up.
Am I missing something here?
class NewViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var scrollView: UIScrollView!
var activeTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.contentSize.height = 1000
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func registerForKeyboardNotifications() {
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: "keyboardWillBeShown:", name: UIKeyboardWillShowNotification, object: nil)
notificationCenter.addObserver(self, selector: "keyboardWillBeHidden:", name: UIKeyboardWillHideNotification, object: nil)
}
func keyboardWillBeShown(sender: NSNotification) {
let info: NSDictionary = sender.userInfo!
let value: NSValue = info.valueForKey(UIKeyboardFrameBeginUserInfoKey) as! NSValue
let keyboardSize: CGSize = value.CGRectValue().size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(self.scrollView.contentInset.top, 0.0, keyboardSize.height, 0.0)
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardSize.height
print(aRect)
let activeTextFieldRect: CGRect? = activeTextField?.frame
let activeTextFieldOrigin: CGPoint? = activeTextFieldRect?.origin
if (!CGRectContainsPoint(aRect, activeTextFieldOrigin!)) {
scrollView.scrollRectToVisible(activeTextFieldRect!, animated:true)
}
}
func keyboardWillBeHidden(sender: NSNotification) {
let contentInsets: UIEdgeInsets = UIEdgeInsetsZero
scrollView.contentInset = contentInsets
scrollView.scrollIndicatorInsets = contentInsets
}
func textFieldDidBeginEditing(textField: UITextField) {
activeTextField = textField
}
func textFieldDidEndEditing(textField: UITextField) {
activeTextField = nil
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.registerForKeyboardNotifications()
}
override func viewDidDisappear(animated: Bool) {
super.viewWillDisappear(animated)
NSNotificationCenter.defaultCenter().removeObserver(self)
}
Try this code and set "moveValue" for your situation.
Remember to extends UITextFieldDelegate
func textFieldDidBeginEditing(textField: UITextField)
{
self.animateViewMoving(true, moveValue: 85)
}
func textFieldDidEndEditing(textField: UITextField)
{
self.animateViewMoving(false, moveValue: 85)
}
func animateViewMoving (up:Bool, moveValue :CGFloat)
{
let durataMovimento:NSTimeInterval = 0.3
let movimento:CGFloat = ( up ? -moveValue : moveValue)
UIView.beginAnimations( "animateView", context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(durataMovimento )
self.view.frame = CGRectOffset(self.view.frame, 0, movimento)
UIView.commitAnimations()
}