Swift using function from delegated class as selector crashes - swift

I have a custom view class, MyView, which inherits from UIView, and it contains a text field. I have added a delegate variable to this class which represents an instance of my ViewController class. This controller contains a function which I want to use as a selector in addTarget inside MyView:
class ViewController: UIViewController, UITextFieldDelegate {
let my_view = MyView()
override func viewDidLoad() {
super.viewDidLoad()
self.my_view.delegate = self
self.my_view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.my_view)
// set up constraints
}
func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd h:mm a"
self.my_view.time_text_field.text = "\(dateFormatter.string(from: sender.date))"
}
}
class MyView: UIView {
weak var delegate: ViewController! {
didSet {
self.time_text_field.delegate = self.delegate
}
}
lazy var time_text_field: UITextField = {
let text_field = UITextField()
let date_picker = UIDatePicker()
date_picker.addTarget(self, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)
text_field.inputView = date_picker
text_field.translatesAutoresizingMaskIntoConstraints = false
return text_field
}()
init() {
self.addSubview(self.time_text_field)
// set up constraints
}
}
When the function handleDatePicker gets called, the app crashes. However, when I move the function into the MyView class, the app no longer crashes:
class ViewController: UIViewController, UITextFieldDelegate {
let my_view = MyView()
override func viewDidLoad() {
super.viewDidLoad()
self.my_view.delegate = self
self.my_view.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(self.my_view)
// set up constraints
}
}
class MyView: UIView {
weak var delegate: ViewController! {
didSet {
self.time_text_field.delegate = self.delegate
}
}
lazy var time_text_field: UITextField = {
let text_field = UITextField()
let date_picker = UIDatePicker()
date_picker.addTarget(self, action: #selector(self.handleDatePicker(sender:)), for: .valueChanged)
text_field.inputView = date_picker
text_field.translatesAutoresizingMaskIntoConstraints = false
return text_field
}()
init() {
self.addSubview(self.time_text_field)
// set up constraints
}
func handleDatePicker(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd h:mm a"
self.time_text_field.text = "\(dateFormatter.string(from: sender.date))"
}
}
Why does the app crash when the function used in #selector comes from the delegated class? Thanks.

The problem is that I was writing
date_picker.addTarget(self, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)
Instead of self for the first parameter, I should be using self.delegate:
date_picker.addTarget(self.delegate, action: #selector(self.delegate.handleDatePicker(sender:)), for: .valueChanged)

Related

UITapGestureRecognizer not attaching action

I created a separate class for View.
I left all the functions in the Controller.
But when I add a click on the picture, it doesn't work for some reason.
import UIKit
class APOTDView: UIView {
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(APOTDViewController.imageTapped(_:)))
imageView.addGestureRecognizer(tap)
return imageView
}()
}
import UIKit
class APOTDViewController: UIViewController {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
// ... add subview and constraint
}
#objc func imageTapped(_ sender: UITapGestureRecognizer) {
print("good job")
}
}
What's the matter? Please help me figure it out
Your selector in the UITapGestureRecognizer is wrong. You can not call the APOTDViewController directly.
APOTDViewController.imageTapped would be a static function, which is not available.
You can use a delegate instead.
Delegate Protocol and View.
protocol APOTDViewDelegate: AnyObject {
func viewDidTapImage()
}
class APOTDView: UIView {
weak var delegate: APOTDViewDelegate?
var imageView: UIImageView = {
let imageView = UIImageView()
imageView.translatesAutoresizingMaskIntoConstraints = false
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(imageTapped))
imageView.addGestureRecognizer(tap)
return imageView
}()
#objc func imageTapped() {
delegate?.viewDidTapImage()
}
}
ViewController:
class APOTDViewController: UIViewController, APOTDViewDelegate {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
av.delegate = self
// ... add subview and constraint
}
#objc func viewDidTapImage() {
print("good job")
}
}
This will not work, because you are calling the UIViewController method directly without any class reference or object. The solution is to use protocol or clouser to get action from view to class.
class
class APOTDView: UIView {
#objc var imageViewAction: ((UITapGestureRecognizer) -> Void)? = nil
lazy var imageView: UIImageView = {
let imageView = UIImageView()
imageView.backgroundColor = .blue
imageView.translatesAutoresizingMaskIntoConstraints = true
imageView.isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector((imageTapped(_:))))
imageView.addGestureRecognizer(tap)
return imageView
}()
#objc private func imageTapped(_ sender: UITapGestureRecognizer) {
self.imageViewAction?(sender)
}
}
ViewController
class APOTDViewController: UIViewController {
let av = APOTDView()
override func viewDidLoad() {
super.viewDidLoad()
av.imageViewAction = { sender in
print("good job")
}
}
}

How To Update Data in Another View Controller without Segue (SideMenu)

I am using SideMenu and I am trying to add an item to that SideMenu from another View Controller.
I store the items for SideMenu in habits object. But I have no idea how to add a new item from ADD VIEW CONTROLLER as there is no segue.
To sum up;
How can I access/edit habits object in SideMenu from "Add View Controller"
Here is my code for SideMenu;
import UIKit
import SideMenu
import FSCalendar
class MenuListController: UITableViewController {
var habits = [Habit]()
var selectedHabitIndex = 0
let darkColor = UIColor(red: 33/255.0, green: 33/255.0, blue: 33/255.0, alpha: 1)
override func viewDidLoad() {
super.viewDidLoad()
tableView.reloadData()
tableView.backgroundColor = darkColor
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
// MARK: - HABIT INITILIZAR
var dateStrings = ["2020-12-25","2020-12-24","2020-12-23","2020-12-22"]
var dateObjects = [Date]()
let dateFormatter = DateFormatter()
for date in dateStrings{
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateObject = dateFormatter.date(from: date)
dateObjects.append(dateObject!)
}
let formatter = DateFormatter()
formatter.dateFormat = "yyyy/MM/dd"
let someDate = formatter.date(from: "2020/12/29")
habits = [Habit(name: "Read a book", selectedDatesArray: dateObjects),
Habit(name: "Go for a walk", selectedDatesArray: dateObjects)
]
}
// MARK: - Number of Habits in the Table View
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return habits.count
}
// MARK: - Display Names of the Habits in the Table View
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = habits[indexPath.row].name
cell.textLabel?.textColor = .white
cell.backgroundColor = darkColor
return cell
}
}
Main View Controller
import UIKit
import SideMenu
import FSCalendar
class ViewController: UIViewController, FSCalendarDelegate, FSCalendarDelegateAppearance {
var selectedDateArray : [Date] = []
var habits = [Habit]()
var menu: SideMenuNavigationController?
var selectedHabit: Habit?
#IBOutlet weak var calendar: FSCalendar!
var selectedDate = NSDate()
override func viewDidLoad() {
super.viewDidLoad()
calendar.delegate = self
calendar.scrollDirection = .vertical
calendar.allowsMultipleSelection = true
calendar.locale = NSLocale(localeIdentifier: "tr") as Locale
menu = SideMenuNavigationController(rootViewController: MenuListController())
menu?.leftSide = true
menu?.setNavigationBarHidden(true, animated: false)
SideMenuManager.default.leftMenuNavigationController = menu
SideMenuManager.default.addPanGestureToPresent(toView: self.view)
var dateStrings = ["2020-12-25","2020-12-24","2020-12-23","2020-12-22"]
var dateObjects = [Date]()
let dateFormatter = DateFormatter()
for date in dateStrings{
dateFormatter.dateFormat = "yyyy-MM-dd"
let dateObject = dateFormatter.date(from: date)
dateObjects.append(dateObject!)
}
habits = [Habit(name: "Read a book", selectedDatesArray: dateObjects),
Habit(name: "Go for a walk", selectedDatesArray: dateObjects)
]
}
func toggleSideBar() {
present(menu!, animated: true, completion: nil)
}
#IBAction func didMenuTapped(_ sender: UIButton) {
present(menu!, animated: true, completion: nil)
}
func showSelectedDates (habit: Habit) {
calendar.select(habit.selectedDatesArray)
}
func setTitle(habit: Habit) {
title = habit.name
}
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
selectedDateArray.append(date)
}
func updateUI() {
setTitle(habit: selectedHabit ?? habits[0] )
showSelectedDates(habit: selectedHabit ?? habits[0])
}
}
Add Habit View Controller
import UIKit
class AddHabitViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addbuttonTapped(_ sender: UIButton) {
// NO IDEA WHAT TO PUT HERE?
self.dismiss(animated: true, completion: nil)
}
}
protocol MenuVCDataFillDelegate : class {
func addNewData(data: Habit)
}
class MenuListController : UITableViewController,MenuVCDataFillDelegate {
func addNewData(data: Habit){
habits.append(data)
}
}
Then, define your main ViewController like this:
class ViewController : UIViewController, AddHabitDelegate {
weak var menuDelegate : MenuVCDataFillDelegate?
func addMenuVC(){
let vc = MenuListController()
self.menuDelegate = vc
}
func navigateToAddHabit(){
let vc = AddHabitViewController()
vc.delegate = self
}
func newHabitAdded(data: Habit){
delegate?.addNewData(data: data)
}
}
And modify your AddHabitViewController to fill this data when added:
protocol AddHabitDelegate : class {
func newHabitAdded(data: Habit)
}
class AddHabitViewController: UIViewController {
weak var delegate : AddHabitDelegate?
func needsAddItem(data: Habit){
delegate?.newHabitAdded(data: data)
}
}
You can use closure or delegates pattern to pass your habit object. Implement the code below by checking, you can use this sample pattern.
// AddHabitViewController
typealias SuccessListener = (Habit) -> ()
var successListener: SuccessListener?
#IBAction func addbuttonTapped(_ sender: UIButton) {
self.dismiss(animated: false, completion: {
passTheHabitObj = Habit(name: "yourString", selectedDatesArray: dateObject)
self.successListener?(passTheHabitObj)
})
}
// ViewController
var habit = [HabitArray]() // use your array for data
let sb = UIStoryboard(name: "Main", bundle: nil)
if let destVC = sb.instantiateViewController(withIdentifier: "AddHabitViewController") as? AddHabitViewController {
destVC.successListener = { habitObj in
habit.append(habitObj)
}
thanks all for the answers. I tried both approaches but could not make it work.
Using the notification center solved my problem;
In AddHabitVC;
NotificationCenter.default.post(name: Notification.Name("ourCustom"), object: textField.text)
In SideMenuVC:
var observer : NSObjectProtocol?
observer = NotificationCenter.default.addObserver(forName: Notification.Name("ourCustom"), object: nil, queue: .main, using: { (notification) in
guard let object = notification.object as? String else {
return
}
self.items.append(object)
self.tableView.reloadData()
})
Thanks.

Creating a JTAppleCalendar inside a xib view

I'm trying to create a calendar using JTAppleCalendar inside a xib view because I want a paging type thing for it. On the first page is some information, and the second page is the calendar. I followed the tutorials for JTAppleCalendar on multiple sites, but my project always crashes, probably because I'm doing something wrong.
I have 3 files, a DetailViewController for setting up and displaying the pages, a dataViewController to connect the xib vars, and a CollectionViewCell for the calendar cells.
DetailViewController
import UIKit
import JTAppleCalendar
class DetailViewController: UIViewController {
var sportName: String = ""
var numClasses: Int = 0
var pages : [dataViewController]{
get {
let page1: dataViewController = Bundle.main.loadNibNamed("dataView", owner: self, options: nil)?.first as! dataViewController
page1.label.text = sportName
page1.classDuration.text = String(numClasses)
let page2: dataViewController = Bundle.main.loadNibNamed("secondDataView", owner: self, options: nil)?.first as! dataViewController
page2.calendar.calendarDataSource = self as? JTACMonthViewDataSource
page2.calendar.calendarDelegate = self as? JTACMonthViewDelegate
page2.calendar.reloadData()
return [page1, page2]
}
}
#IBOutlet weak var detailDescriptionLabel: UILabel!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var pageControl: UIPageControl!
func configureView() {
// Update the user interface for the detail item.
if let detail = detailItem {
if let label = detailDescriptionLabel {
label.text = detail.description
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let back = UIBarButtonItem(title: "Back",style: .plain,target: self,action: #selector(backButton(_:)))
self.navigationItem.leftBarButtonItem = back
configureView()
view.bringSubviewToFront(pageControl)
setupScrollView(pages: pages)
pageControl.numberOfPages = pages.count
pageControl.currentPage = 0
}
var detailItem: String? {
didSet {
// Update the view.
configureView()
}
}
#objc
func backButton (_ sender: Any) {
performSegue(withIdentifier: "detailToList", sender: self)
}
func setupScrollView(pages: [dataViewController]){
scrollView.frame = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height)
scrollView.contentSize = CGSize(width: view.frame.width * CGFloat(pages.count), height: view.frame.height)
scrollView.isPagingEnabled = true
for i in 0 ..< pages.count {
pages[i].frame = CGRect(x: view.frame.width * (CGFloat(i)), y: 0, width: view.frame.width, height: view.frame.height)
scrollView.addSubview(pages[i])
}
}
}
extension DetailViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/view.frame.width)
pageControl.currentPage = Int(pageIndex)
}
}
extension dataViewController: JTACMonthViewDataSource, JTACMonthViewDelegate {
func configureCalendar(_ calendar: JTACMonthView) -> ConfigurationParameters {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy MM dd"
formatter.timeZone = Calendar.current.timeZone
formatter.locale = Calendar.current.locale
let startDate = formatter.date(from: "2020 01 01")!
let endDate = Date()
return ConfigurationParameters(startDate: startDate, endDate: endDate)
}
func calendar(_ calendar: JTACMonthView, cellForItemAt date: Date, cellState: CellState, indexPath: IndexPath) -> JTACDayCell {
let cell = calendar.dequeueReusableJTAppleCell(withReuseIdentifier: "dateCell", for: indexPath) as! CollectionViewCell
cell.dateLabel.text = cellState.text
return cell
}
func calendar(_ calendar: JTACMonthView, willDisplay cell: JTACDayCell, forItemAt date: Date, cellState: CellState, indexPath: IndexPath) {
let cell = cell as! CollectionViewCell
cell.dateLabel.text = cellState.text
}
}
dataViewController
import JTAppleCalendar
class dataViewController: UIView {
//Page 1
#IBOutlet weak var label: UILabel!
#IBOutlet weak var classDuration: UILabel!
//Page 2
#IBOutlet weak var calendar: JTACMonthView!
}
CollectionViewCell
import UIKit
import JTAppleCalendar
class CollectionViewCell: JTACDayCell {
#IBOutlet weak var dateLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
And of course, the xib files. In DetailViewController, I moved the last extension part to dataViewController, but it didn't change anything. I'm also pretty sure I did many things wrong or inefficiently.
Can anyone help successfully implement the JTAppleCalendar inside the xib view, that would be appreciated! Thanks!
In your DetailViewController You are setting delegate and dataSource of calendar to self which is not conforming those protocols.
page2.calendar.calendarDataSource = self as? JTACMonthViewDataSource
page2.calendar.calendarDelegate = self as? JTACMonthViewDelegate
If thoose are needed to be handled in DetailViewController, I suggest you to start with implementing methods in DetailViewController not in dataViewController
extension DetailViewController: JTACMonthViewDataSource, JTACMonthViewDelegate

Why is doubleAction working only when it's handled in a view controller class?

I have a view controller MyViewController:
class MyViewController: NSViewController {
private let componentList = ComponentList()
override func loadView() {
componentList.createView(view)
componentList.myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(componentList.myTableView.clickedRow)")
}
}
This double click action works without problem. However, when I try to put this double click action inside ComponentList, it's not working (action function is not called):
class ComponentList: NSObject, NSTableViewDelegate, NSTableViewDataSource {
let myTableView = NSTableView()
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
}
#objc func doubleClickOnRow() {
print("some row clicked = \(myTableView.clickedRow)")
// never being called, why is that?
}
}
Why isn't the double action handling in ComponentList not working? Am I missing something here?
You need to set both target and doubleAction to make it work with ComponentList
override func createView(view: NSView) {
let scrollView = NSScrollView()
view.addSubview(scrollView)
scrollView.documentView = myTableView
// set up some constraints, ignore here...
myTableView.delegate = self
myTableView.dataSource = self
myTableView.doubleAction = #selector(doubleClickOnRow)
myTableView.target = self // self here is ComponentList
}

Swift datepicker in popovercontroller not updating the correct label

In my app i have added 2 labels and 2 buttons, the buttons are hooked to a function that pops a datepicker in popovercontroller, when user select a date the corresponding label should change.
Everything work except the labels are updated together, what I want is update only the relevant label, i.e. when when button1 is pressed label1 should recieve datepicker date and vice versa.
Here is my code:
import UIKit
class PopoverTestVC: UIViewController, UIPopoverPresentationControllerDelegate {
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var dateLabel2: UILabel!
#IBOutlet weak var selectButton: UIButton!
#IBOutlet weak var selectButton2: UIButton!
#IBOutlet var datePicker: UIDatePicker!
var dateString = ""
let vc = UIViewController()
override func viewDidLoad() {
super.viewDidLoad()
datePicker.timeZone = NSTimeZone(abbreviation: "GMT")
//datePicker.addTarget(self, action: Selector("datePicked:"), forControlEvents: UIControlEvents.ValueChanged)
}
func datePicked(sender: UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd MMM yyyy HH:mm"
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
dateLabel.text = dateFormatter.stringFromDate(datePicker.date)
}
func datePicked2(sender: UIDatePicker) {
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd MMM yyyy HH:mm"
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
dateLabel2.text = dateFormatter.stringFromDate(datePicker.date)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func popUp(sender: UIButton)
{
let fr = datePicker.frame
vc.view.frame = fr
vc.view.addSubview(datePicker!)
vc.view.backgroundColor = UIColor.greenColor()
vc.preferredContentSize = datePicker.frame.size
vc.modalPresentationStyle = UIModalPresentationStyle.Popover
if sender == selectButton
{
datePicker.addTarget(self, action: Selector("datePicked:"), forControlEvents: UIControlEvents.ValueChanged)
}
else if sender == selectButton2
{
datePicker.addTarget(self, action: Selector("datePicked2:"), forControlEvents: UIControlEvents.ValueChanged)
}
let popOverPC = vc.popoverPresentationController
vc.popoverPresentationController?.sourceRect = sender.frame
vc.popoverPresentationController?.sourceView = view
popOverPC!.permittedArrowDirections = UIPopoverArrowDirection.Any
popOverPC!.delegate = self
presentViewController(vc, animated: true, completion: nil)
}
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle
{
return UIModalPresentationStyle.None
}
}
Appreciate any help or suggestion to do that in better way as the app might have several outlets that use the same datepicker.
I'd move your date picker to a custom class (this also lets you use a xib to do some cool design stuff) call this from your view controller setting a var to the button that called the date picker. Create a protocol in the date picker and implement the delegate in the parent view controller populating whatever labels you require based on the button that called the date picker.
For example, in my app I have several text fields that must contain dates. When the user begins to edit the text field I launch a custom popover from textFieldShouldBeginEditing and assign the text field to a var.
The parent view controller is set as the delegate to a protocol in my custom date picker:
// MARK: - Textfield delegate
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
textField.backgroundColor = UIColor.whiteColor()
if (textField.tag == 500 || textField.tag == 501)
{
activeTextField = textField
let vc = BGSDatePickerVC(nibName: "BGSDatePickerVC", bundle: nil)
vc.modalPresentationStyle = UIModalPresentationStyle.Popover
if textField.tag == 500{
vc.popoverPresentationController?.sourceView = viewMarkerFromDate
}else
{
vc.popoverPresentationController?.sourceView = viewMarkerToDate
}
vc.popoverPresentationController?.permittedArrowDirections = UIPopoverArrowDirection.Up
vc.preferredContentSize = CGSizeMake(vc.view.frame.width, vc.view.frame.height)
vc.delegate = self
self.presentViewController(vc, animated: true, completion: nil)
return false
}
return true
}
The protocol in my custom date picker popover:
protocol BGSDatePickerVCProtocol
{
func bgsDatePickerUse(date: NSDate, strDate: String)
}
class BGSDatePickerVC: UIViewController {
var delegate:BGSDatePickerVCProtocol! = nil
and the implementation in the calling view controller:
// MARK: - Delegates
// MARK: BGSDatePickerVCProtocol
func bgsDatePickerUse(date: NSDate, strDate: String)
{
if activeTextField.tag == 500
{
dateSelectedFrom = date
}
if activeTextField.tag == 501
{
dateSelectedTo = date
}
activeTextField.text = strDate
}