User Input Data does not remain after viewing other ViewControllers - ios

The user input is typed into the TextFields and TextViews on ViewController1, but when I select a button to show either ViewController2 or ViewController3 via a segue, on my return to ViewController1 (also via another segue), the input is no longer there, as if the app was just reopened.
How do I make the initial user input remain in the text fields and text views while the user switches to a different view and also until the user hits the "Send Email" button back on ViewController1?
Below is my code
ViewController1
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var DateTextField: UITextField!
#IBOutlet weak var ScrollView: UIScrollView!
#IBOutlet weak var FirstTextView: UITextField!
#IBOutlet weak var FirstName: UITextField!
#IBOutlet weak var OtherDetailsField: UITextView!
lazy var datePicker: UIDatePicker = {
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.addTarget(self, action: #selector(datePickerChanged(_:)), for: .valueChanged)
return picker
}()
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return formatter
}()
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
// let selectedRange = OtherDetailsField.selectedRange
// OtherDetailsField.scrollRangeToVisible(selectedRange)
}
override func viewDidLoad(){
super.viewDidLoad()
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
// Date Picker ---------------
DateTextField.inputView = datePicker}
#objc func datePickerChanged(_ sender: UIDatePicker){
DateTextField.text = dateFormatter.string(from: sender.date)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){view.endEditing(true)
}
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
#objc func dismissKeyboard() {
view.endEditing(true)
}
}
// Add Done Button to keypad toolbar -----------------
extension UITextField{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction()
{
self.resignFirstResponder()
}
}
extension UITextView{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard() {
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction() {
self.resignFirstResponder()
}
}
ViewController2
import UIKit
class ViewController2: UIViewController {
#IBOutlet weak var ScrollView: UIScrollView!
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
//let selectedRange = yourTextView.selectedRange
//yourTextView.scrollRangeToVisible(selectedRange)
}
override func viewDidLoad() {
super.viewDidLoad()
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
func dismissKeyboard()
{
view.endEditing(true)
}
}
ViewController3
import UIKit
class ViewController3: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}

short answer: backwards segues that You used create new instances, You could use 'dismiss' method instead or use Tab bar controller.
long answer:
Firstly, I assume You created Single View Controller when creating project in Xcode, and all segues You used are of "show" type.
Lets say You create segue 'ViewController1->ViewController2' and button 'NEXT' to execute it.
When You click on 'NEXT' You create a new instance of ViewController2, something kinda "new copy".
Next You create another segue to go back:
'ViewController2->ViewController1' and button 'BACK' to execute.
when You click on 'BACK' button (which triggers segue), You're NOT going back to the original ViewController1, but You're creating a new instance of ViewController1.
So as You can see, using segues this way can create future memory problems when users could go back and forth, every time creating new instance, stacking windows on top of each other.
The easiest solution is to delete segues that point backwards and instead put 'dismiss' method inside your 'BACK' button codeblock:
self.dismiss(animated: true, completion: nil)
Every time You use 'dismiss' method, You close the actual ViewController and go back to the previous one.
If You want something like 'HOME' button to go from any ViewController back to the first one (root), You can use this code:
self.view.window!.rootViewController?.dismiss(animated: true,completion: nil)
But remember - If You don't write some code to save Your data before dismissing, You will still lose the ViewController2 and ViewController3 data when You dismiss them.
Finally, if You need to keep Your data displayed when user switches between ViewControllers, I would use Tab bar controller. You can create it using template 'Tabbed App' when You create Xcode project.
Hope this helps! And don't take it as 100%, I'm still a Swift Rookie :)

Related

Application stops after swiping

I have an app reader like Apple Books. And I have a problem. I have NavigationViewController next FirstViewController with book button, SecondViewController it is a reader and TableViewController it is settings for text size. But if I open book in FirstViewController go to SecondViewController and in navigation bar tab aA button and open TableViewController all works fine. But after if I want back to FirstViewController I not close TableViewController and do swipe from left to right my app stops. How to fix it?
SecondViewController:
#IBOutlet weak var textSettings: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupGestures()
}
// MARK: - Text Settings View
private func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapped))
tapGesture.numberOfTapsRequired = 1
textSettings.addGestureRecognizer(tapGesture)
}
#objc private func tapped(){
guard let popVC = storyboard?.instantiateViewController(withIdentifier: "popVC") else { return }
popVC.modalPresentationStyle = .popover
let popOverVC = popVC.popoverPresentationController
popOverVC?.delegate = self
popOverVC?.sourceView = self.textSettings
popOverVC?.sourceRect = CGRect(x: self.textSettings.bounds.midX, y: self.textSettings.bounds.maxY, width: 0, height: 0)
popVC.preferredContentSize = CGSize(width: 250, height: 250)
self.present(popVC, animated: true)
}
}
extension SecondViewController: UIPopoverPresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}
Other controllers have no special code. Video illustrating the problem - https://drive.google.com/file/d/1HUV26H4VFoKglh8FbkNP0y2Y3rYl4Q_f/view?usp=sharing

I can't pass data between classes using callback as completion handler

I want to pass text of textField in custom class to ViewController and populate it to array when BarButtonItem of DatePicker is tapped. I used a callback as completion handler, but it caught EXC-BAD-ACCESS. What made this error and how could I pass text to my ViewController?
Custom class of textField
class HourDatePicker: UITextField {
var datePicker = UIDatePicker()
override init(frame: CGRect) {
super.init(frame: frame)
commominit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commominit()
}
func commominit(){
text = ""
datePicker.datePickerMode = .dateAndTime
datePicker.minuteInterval = 30
datePicker.locale = Locale(identifier: "ja")
datePicker.addTarget(self, action: #selector(setText), for: .valueChanged)
setText()
inputView = datePicker
inputAccessoryView = customPicker()
}
#objc func setText(){
let f = DateFormatter()
f.dateStyle = .full
f.timeStyle = .short
f.locale = Locale(identifier: "ja")
textColor = .black
text = "\(f.string(from: datePicker.date))"
}
private func customPicker() -> UIToolbar {
let toolbar = UIToolbar()
toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: 40)
let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: self, action: nil)
space.width = 100
let flexSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let todayButtonItem = UIBarButtonItem(title: "today", style: .done, target: self, action: #selector(setToday))
let selectButtonItem = UIBarButtonItem(title: "select", style: .done, target: self, action: #selector(tellCalenderText))
let toolbarItem = [space, flexSpaceItem, todayButtonItem, selectButtonItem]
toolbar.setItems(toolbarItem, animated: true)
}
return toolbar
}
#objc func tellCalenderText(completion: ((_ titleText: String) -> Void)){
//I want to pass text here.
if text != "" {
guard let titleText = text else {return}
completion(titleText)
} else {
return
}
}
ViewController
class Calender1ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let selectDate = HourDatePicker()
private var keepDate: [String] = []
#IBOutlet weak var timeTextView: UITextView!
#IBOutlet weak var dateText: HourDatePicker!
#IBOutlet weak var calenderTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.calenderTableView.delegate = self
self.calenderTableView.dataSource = self
selectDate.tellCalenderText {[weak self] (titleText) in
self?.bringDate(title: titleText)
}
}
func bringDate(title: String){
print("title: \(title)")
self.keepDate.append(title)
timeTextView.text.append(contentsOf: "\(title)\n")
}
Thank you.
This is error log.
error log
First, I think you're getting a bit messed up by having:
private let selectDate = HourDatePicker()
and having:
#IBOutlet weak var dateText: HourDatePicker!
and then making use of selectDate inside viewDidLoad()...
Give this a try. I only made a few changes, and tried to include enough comments to make it clear. I think you'll find this a much more straight-forward way of getting your custom HourDatePicker class to pass information back to the view controller:
class HourDatePicker: UITextField {
var datePicker = UIDatePicker()
override init(frame: CGRect) {
super.init(frame: frame)
commominit()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commominit()
}
func commominit(){
text = ""
datePicker.datePickerMode = .dateAndTime
datePicker.minuteInterval = 30
datePicker.locale = Locale(identifier: "ja")
datePicker.addTarget(self, action: #selector(setText), for: .valueChanged)
setText()
inputView = datePicker
inputAccessoryView = customPicker()
}
#objc func setToday(){
datePicker.date = Date()
}
#objc func setText(){
let f = DateFormatter()
f.dateStyle = .full
f.timeStyle = .short
f.locale = Locale(identifier: "ja")
textColor = .black
text = "\(f.string(from: datePicker.date))"
}
private func customPicker() -> UIToolbar {
let toolbar = UIToolbar()
toolbar.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: 40)
let space = UIBarButtonItem(barButtonSystemItem: .fixedSpace, target: self, action: nil)
space.width = 100
let flexSpaceItem = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
let todayButtonItem = UIBarButtonItem(title: "today", style: .done, target: self, action: #selector(setToday))
let selectButtonItem = UIBarButtonItem(title: "select", style: .done, target: self, action: #selector(tellCalenderText))
let toolbarItem = [space, flexSpaceItem, todayButtonItem, selectButtonItem]
toolbar.setItems(toolbarItem, animated: true)
return toolbar
}
// "callback" closure
var tellController: ((String) ->())?
// triggered by "select" bar button tap
#objc func tellCalenderText() -> Void {
// get text from self
guard let t = text else {
return
}
// execute the callback closure
tellController?(t)
}
// #objc func tellCalenderText(completion: ((_ titleText: String) -> Void)){
// //I want to pass text here.
// if text != "" {
// guard let titleText = text else {return}
// completion(titleText)
// } else {
// return
// }
//
// }
}
//ViewController
class Calender1ViewController: UIViewController {
// not needed
//private let selectDate = HourDatePicker()
private var keepDate: [String] = []
// UITextView connected via Storyboard
#IBOutlet weak var timeTextView: UITextView!
// UITextField set to HourDatePicker, connected via Storyboard
#IBOutlet weak var dateText: HourDatePicker!
#IBOutlet weak var calenderTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// set the callback closure for the HourDatePicker
dateText.tellController = { [weak self] (titleText) in
self?.bringDate(title: titleText)
}
// not needed
//selectDate.tellCalenderText {[weak self] (titleText) in
// self?.bringDate(title: titleText)
//}
}
func bringDate(title: String){
print("title: \(title)")
self.keepDate.append(title)
timeTextView.text.append(contentsOf: "\(title)\n")
}
}

I want email address from 2nd ViewController to be used for mail.setToRecipients([myTextField.text ?? ""])

I have 2 ViewControllers, the second one has a textField which the user can set their default email address in, so they do not have to input it every time the app is to send off an email.
The button to send email is on the first ViewController.
After entering comments, details etc, they hit the "send" email button and the standard mail composer pops up.
Now here is where the To recipients should be automatically set to their default email address which is from the text field back on the second ViewController.
The ViewControllers are setup with basic segues (show)
I don't know how to pass data between ViewControllers, I only recently started to code, so i am very new at this.
I have the below code so far
ViewController1
// Created by Mark Smith on 14/7/19.
// Copyright © 2019 Mark Smith. All rights reserved.
//
import UIKit
import MessageUI
class ViewController: UIViewController, MFMailComposeViewControllerDelegate
{
//// Outlets //////////////////////////////////////////////////////////
#IBOutlet weak var DateTextField: UITextField!
#IBOutlet weak var ScrollView: UIScrollView!
#IBOutlet weak var FirstTextView: UITextField!
#IBOutlet weak var FirstName: UITextField!
#IBOutlet weak var LastName: UITextField!
#IBOutlet weak var Street: UITextField!
#IBOutlet weak var Area: UITextField!
#IBOutlet weak var PostCode: UITextField!
#IBOutlet weak var Phone: UITextField!
#IBOutlet weak var Email: UITextField!
#IBOutlet weak var Reference: UITextView!
#IBOutlet weak var OtherDetailsField: UITextView!
#IBOutlet weak var SendEmail: UIBarButtonItem!
#IBOutlet weak var ReferenceTextField: UITextView!
/////-------------------------------------------------
/////-----------------------
lazy var datePicker: UIDatePicker = {
let picker = UIDatePicker()
picker.datePickerMode = .date
picker.addTarget(self, action: #selector(datePickerChanged(_:)), for: .valueChanged)
return picker
}()
lazy var dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
return formatter
}()
// Send Email -----------------------------------
#IBAction func SendEmail(_ sender: UIBarButtonItem) {
//MARK: IBAction Method for Button click
//TODO: You should check if we can send email or not
if MFMailComposeViewController.canSendMail() {
let mail = MFMailComposeViewController()
mail.mailComposeDelegate = self
mail.setToRecipients([Email.text ?? ""])
mail.setSubject("Customer Called Today")
mail.setMessageBody("Date: \(String(describing: DateTextField.text ?? "nil"))</br>\nFirst Name: \(String(describing: FirstName.text ?? "nil"))</br>\nLast Name: \(String(describing: LastName.text ?? "nil"))</br>\nStreet: \(String(describing: Street.text ?? "nil"))</br>\nArea: \(String(describing: Area.text ?? "nil"))</br>\nPost Code: \(String(describing: PostCode.text ?? "nil"))</br>\nPhone: \(String(describing: Phone.text ?? "nil"))</br>\nEmail: \(String(describing: Email.text ?? "nil"))</br>\nReference: \(String(describing: Reference.text ?? "nil"))</br>\nOther Details: \(String(describing: OtherDetailsField.text ?? "nil")) ", isHTML: true)
// mail.setMessageBody = DateTextField.text
//Body = textBox3.Text;
present(mail, animated: true)
} else {
print("Application is not able to send an email")
}
}
//MARK: MFMail Compose ViewController Delegate method
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
// Dismiss the mail compose view controller.
controller.dismiss(animated: true, completion: nil)
}
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
// let selectedRange = OtherDetailsField.selectedRange
// OtherDetailsField.scrollRangeToVisible(selectedRange)
}
//// View Did Load ///////////////////////////////////////
override func viewDidLoad(){
super.viewDidLoad()
// Do any additional setup after loading the view.
//Set date as today's date
let todaysDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MMMM yyyy"
let dateString = dateFormatter.string(from: todaysDate)
DateTextField.text = dateString
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
// Date Picker ---------------
DateTextField.inputView = datePicker}
#objc func datePickerChanged(_ sender: UIDatePicker){
DateTextField.text = dateFormatter.string(from: sender.date)
}
// `$`(".date-pick").datepicker();
// `$`(".date-pick").datepicker("setDate",new, Date());
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?){view.endEditing(true)
}
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
#objc func dismissKeyboard()
{
view.endEditing(true)
}
///////
}
// Add Done Button to keypad toolbar -----------------
extension UITextField{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard()
{
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction()
{
self.resignFirstResponder()
}
}
extension UITextView{
#IBInspectable var doneAccessory: Bool{
get{
return self.doneAccessory
}
set (hasDone) {
if hasDone{
addDoneButtonOnKeyboard()
}
}
}
func addDoneButtonOnKeyboard()
{
let doneToolbar: UIToolbar = UIToolbar(frame: CGRect.init(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
doneToolbar.barStyle = .default
let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(self.doneButtonAction))
let items = [flexSpace, done]
doneToolbar.items = items
doneToolbar.sizeToFit()
self.inputAccessoryView = doneToolbar
}
#objc func doneButtonAction()
{
self.resignFirstResponder()
}
}
ViewController2
// Created by Mark Smith on 18/7/19.
// Copyright © 2019 Mark Smith. All rights reserved.
//
import UIKit
class ViewController2: UIViewController {
//// Outlets //////////////////////////////////////////////////////////
#IBOutlet weak var ScrollView: UIScrollView!
#IBOutlet weak var DefaultSubject: UITextField!
#IBOutlet weak var DefaultEmail: UITextField!
// Adjust Scroll for Keyboard ------------------
#objc func adjustForKeyboard(notification: Notification) {
guard let keyboardValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
let keyboardScreenEndFrame = keyboardValue.cgRectValue
let keyboardViewEndFrame = view.convert(keyboardScreenEndFrame, from: view.window)
if notification.name == UIResponder.keyboardWillHideNotification {
ScrollView.contentInset = .zero
} else {
ScrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: keyboardViewEndFrame.height - view.safeAreaInsets.bottom, right: 0)
}
ScrollView.scrollIndicatorInsets = ScrollView.contentInset
//let selectedRange = yourTextView.selectedRange
//yourTextView.scrollRangeToVisible(selectedRange)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Adjust Scroll for Keyboard ---------------
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
notificationCenter.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
}
/*
// 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.
}
*/
// Dismiss Keyboard ------------------
func setupKeyboardDismissRecognizer(){
let tapRecognizer: UITapGestureRecognizer = UITapGestureRecognizer(
target: self, action: #selector(ViewController.dismissKeyboard))
tapRecognizer.cancelsTouchesInView = false
self.view.addGestureRecognizer(tapRecognizer)
}
func dismissKeyboard()
{
view.endEditing(true)
}
}
You can achieve this by using Delegation Pattern,
Step 1
protocol EmailSubmissionDelegate {
func emailSubmitted(email:String)
}
class ViewController2: UIViewController {
var emailSubmissionDelegate:EmailSubmissionDelegate?
//rest of the code...
}
and wherever user set the default email in ViewController2, call,
emailSubmissionDelegate?.emailSubmitted(email: "<#T##String#>")
Step 2
Implement the protocol in ViewController
class ViewController: UIViewController, MFMailComposeViewControllerDelegate, EmailSubmissionDelegate {
func emailSubmitted(email: String) {
/* Add the email to the textfield
E.g:
emailTextField.text = email
*/
}
}
Step 3
Make sure you initialize the delegate in prepare for segue method in ViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let viewController2 = segue.destination as! ViewController2
viewController2.emailSubmissionDelegate = self
}
I have solved my own question in a fairly easy way.
I am already saving UserDefaults globally.
There is a TextField on ViewController2 which has the app user's saved default email address, which is the email destination that they will regularly send to.
This email is saved and retrieved through UserDefaults
My issue was that the code in the MFMailComposeViewController needed to link to an Outlet textfield, but in the same ViewController as itself in order to use the email address in that TextField.
So, in this ViewController i simply created a "dummy" textField named the same as the one in ViewController2 and used the UserDefauts to retrieve the email.
Then i simply made the textFiled hidden (after constraining it so it wont float and cause an issue).
Now works perfectly.
I also managed to be able to set the "to recipient address", again sourcing the preferred "to" email address the same way.
Note: I used
mail.setPreferredSendingEmailAddress(EmailFrom.text ?? "")
My textField is call "EmailFrom"

How to add buttons above keyboard

How to add button above the keyboard like this one in Stack Exchange app? And when you long press the text in UITextView How to add "Select" and "Select All"?
The first question, you can set textField's inputAccessoryView to your custom view, this can customize the keyboard's header.
The result:
You can do it like below;
first, you should instance the view you want to add above the keyboard.
class ViewController : UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.inputAccessoryView = Bundle.main.loadNibNamed("CustomAccessoryView", owner: self, options: nil)?.first as! UIView?
In your CustomAccessoryView, you can set the action of the button:
import UIKit
class CustomAccessoryView: UIView {
#IBAction func clickLoveButton(_ sender: UIButton) {
print("Love button clicked")
}
}
I would recommend to create a toolbar for your UITextField's accessoryView property.
The idea is to add this toolbar once, before the textfield would show for the first time. Therefore, we assign the delegate to self, and override the textFieldShouldBeginEditing delegate call with our implementation to add the accessoryView.
Here is a simple example, how can u achieve it:
import UIKit
class ViewController: UIViewController {
// your `UITextfield` instance
// Don't forget to attach it from the IB or create it programmaticly
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Assign the delegate to self
textField.delegate = self
}
}
// MARK: Create extension to conform to UITextfieldDelegate
extension ViewController: UITextFieldDelegate {
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
setupTextFieldsAccessoryView()
return true
}
func setupTextFieldsAccessoryView() {
guard textField.inputAccessoryView == nil else {
print("textfields accessory view already set up")
return
}
// Create toolBar
let toolBar: UIToolbar = UIToolbar(frame:CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 44))
toolBar.barStyle = UIBarStyle.black
toolBar.isTranslucent = false
// Add buttons as `UIBarButtonItem` to toolbar
// First add some space to the left hand side, so your button is not on the edge of the screen
let flexsibleSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil) // flexible space to add left end side
// Create your first visible button
let doneButton: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.done, target: self, action: #selector(didPressDoneButton))
// Note, that we declared the `didPressDoneButton` to be called, when Done button has been pressed
toolBar.items = [flexsibleSpace, doneButton]
// Assing toolbar as inputAccessoryView
textField.inputAccessoryView = toolBar
}
func didPressDoneButton(button: UIButton) {
// Button has been pressed
// Process the containment of the textfield or whatever
// Hide keyboard
textField.resignFirstResponder()
}
}
This should be your output:
You'll have to use the inputAccessoryView of your textfield.
you can put the code snippet below in your viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton(frame: CGRect(x: 0, y: 0, width: view.frame.size.width, height: 60))
button.backgroundColor = UIColor.blue
button.setTitle("NEXT", for: .normal)
button.setTitleColor(UIColor.white, for: .normal)
button.addTarget(self, action: #selector(self. yourButton), for: .touchUpInside)
numtextField.inputAccessoryView = button
}
#objc func nextButton()
{
print("do something")
}
Just copy and paste simple code for you accessory button embedded with keypad
func addKeyboardToolbar() {
let ViewForDoneButtonOnKeyboard = UIToolbar()
ViewForDoneButtonOnKeyboard.sizeToFit()
let button = UIButton.init(type: .custom)
button.setImage(UIImage.init(named: "login-logo"), for: UIControlState.normal)
button.addTarget(self, action:#selector(doneBtnfromKeyboardClicked), for:.touchUpInside)
button.frame = CGRect.init(x: 0, y: 0, width:UIScreen.main.bounds.width, height: 30) //CGRectMake(0, 0, 30, 30)
let barButton = UIBarButtonItem.init(customView: button)
ViewForDoneButtonOnKeyboard.items = [barButton]
postTextView.inputAccessoryView = ViewForDoneButtonOnKeyboard
}
func doneBtnfromKeyboardClicked (){
self.contentView.endEditing(true)
}
to add a toolbar with a done button which dismisses the keyboard above a UITextField you can write a UITextField extension with the following function:
public func addAccessoryView() {
let doneButton = UIBarButtonItem.init(barButtonSystemItem: UIBarButtonSystemItem.Done, target: self, action: "resignFirstResponder")
let flexSpace: UIBarButtonItem = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: self, action: nil)
let toolbar = UIToolbar()
toolbar.barStyle = UIBarStyle.Default
toolbar.translucent = true
toolbar.tintColor = Color.blue
toolbar.sizeToFit()
toolbar.setItems([flexSpace, doneButton], animated: false)
toolbar.userInteractionEnabled = true
self.inputAccessoryView = toolbar
}
you can then call the function in your textfield like this:
textfield.addAccessoryView()

"Done" UIBarButtonItem not displaying on number pad (Swift)

I'm trying to add a "Done" button to a text field.
The black UIToolbar is displaying, but the "Done" button is not.
In the attributes inspector, 'Return key' is set to 'Done' and 'Auto-enable Return Key' is enabled.
The UITextField is connected to the delegate
This is inside a UITableViewCell:
import UIKit
class itemTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet var itemName: UILabel!
#IBOutlet var itemInput: UITextField!
var textFieldIsBeingEdited: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
itemInput.delegate = self
self.addDoneButtonToKeyboard()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func addDoneButtonToKeyboard(){
let doneToolbar: UIToolbar = UIToolbar(frame: CGRectMake(0, 0, 320, 50))
doneToolbar.barStyle = UIBarStyle.BlackTranslucent
let flexSpace = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.FlexibleSpace, target: nil, action: nil)
let done: UIBarButtonItem = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.Done, target: itemInput, action: Selector("doneButtonAction"))
let items = NSMutableArray()
items.addObject(flexSpace)
items.addObject(done)
doneToolbar.sizeToFit()
itemInput.inputAccessoryView = doneToolbar
}
func doneButtonAction()
{
self.itemInput.resignFirstResponder()
}
func textFieldShouldReturn(textField: UITextField!) -> Bool {
textField.resignFirstResponder()
return true
}
}
you forget to add the items to your let doneToolbar: UIToolbar
doneToolbar.items = items as [AnyObject]
brief answer
items.addObject(flexSpace)
items.addObject(done)
doneToolbar.items = items as [AnyObject]
doneToolbar.sizeToFit()
itemInput.inputAccessoryView = doneToolbar

Resources