Swift Delegate setting a label from a custom popUp textfield - swift

I have a custom popup view that has a UIDatePicker. This, when changed, changes the date of the save time. I also want the label on the in the CustomCell to be updated if the date has changed. I have used a delegate protocol to update the table but I cannot get this protocol to transfer the information on save. Can you help? I think I have hooked up all the correct code in the viewController class. I have tried this answer but I cannot set the delegate in the target class and there isn't a segue A Swift example of Custom Views for Data Input (custom in-app keyboard)
protocol DatePopUpViewDelegate: class {
func pastDate(date: String) // date that is chosen in picker
func isPastDateSet(isSet: Bool) // has chosen new date
}
#IBDesignable class DatePopUpView: UIView {
var delegate: DatePopUpViewDelegate?
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "DatePopUp", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
delegate?.isPastDateSet(false)
return view
}
// close popup
#IBAction func closeButtonDatePopUp(sender: AnyObject) {
if dateToSave != openTime {
if let dateToSave = dateToSave {
SaveData.changedSaveTime = dateToSave
delegate?.pastDate(dateToSave)
delegate?.isPastDateSet(true)
}
} else {
SaveData.changedSaveTime = ""
delegate?.isPastDateSet(false)
}
}
class SaveTableViewCell: UITableViewCell, DatePopUpViewDelegate {
var changeDateLabel: Bool = false
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
changeDateLabel = false
datePopUpViewControllert.delegate = self
}
// delegate functions
func pastDate(date: String) {
self.labelDate = date
print("del date \(date)")
}
func isPastDateSet(isSet: Bool) {
self.changeDateLabel = isSet
print("is set by delegate \(isSet)")
}

Related

How to listen for data change with #Published variable then reload tableView

The most difficult task I face is to know the correct terminology to search for. I'm used to SwiftUI for an easy way to build an app in the fastest time possible. With this project I have to use UIKit and for this specific task.
Inside a view controller I created a tableView:
private let tableView: UITableView = {
let table = UITableView()
table.register(ProfileCell.self, forCellReuseIdentifier: ProfileCell.identifier)
return table
}()
Later I reload the data inside viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
try await viewModel.getProfiles()
// Here I reload the table when data comes in
self.tableView.reloadData()
} catch {
print(error)
}
}
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
}
So what is viewModel? In SwiftUI I'm used to having this inside a view struct:
#ObservedObject var viewModel = ProfilesViewModel()
..and that's what I have inside my view controller. I've searched for:
observedobject in uitableview
uitableview reload data on data change
..and more but noting useful for me to "pick up the pieces" with.
In same controller, I'm showMyViewControllerInACustomizedSheet which now uses UIHostingController:
private func showMyViewControllerInACustomizedSheet() {
// A SwiftUI view along with viewModel being passed in
let view = ProfilesMenu(viewModel: viewModel)
let viewControllerToPresent = UIHostingController(rootView: view)
if let sheet = viewControllerToPresent.sheetPresentationController {
sheet.detents = [.medium(), .large()]
sheet.largestUndimmedDetentIdentifier = .medium
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
}
For the ProfilesViewModel:
class ProfilesViewModel: ObservableObject {
// ProfilesResponse is omitted
#Published var profiles = [ProfilesResponse]()
public func getProfiles(endpoint: String? = nil) async throws -> Void {
// After getting the data, I set the profiles variable
self.profiles = [..]
}
}
Whenever I call try await viewModel.getProfiles(endpoint: "..."), from ProfileMenu, I'd like to reload the tableView. What additional setup is required?
In the comments, Vadian mentioned "Combine" where I did a Google search and found this. What works, for a basic demonstaration:
[..]
import Combine
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
private let viewModel = ProfilesViewModel()
private var cancellable: AnyCancellable?
override func viewDidLoad() {
super.viewDidLoad()
Task {
do {
try await viewModel.getProfiles()
// Remove this
// self.tableView.reloadData()
} catch {
print(error)
}
}
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
// Add this
cancellable = viewModel.objectWillChange.sink(receiveValue: { [weak self] in
self?.render()
})
}
// Also add this
private func render() {
// TODO: Implement failures...
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
...
}
objectWillChange was the key to my problem.

Change search field's icon

I try to implement search behavior like in Xcode: if you enter something in search field, icon changes color.
I delegate both searchFieldDidStartSearching and searchFieldDidEndSearching to controller and change the image.
The problem is icon's image changes only when window lose it's focus.
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
func searchFieldDidStartSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSActionTemplate")
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSHomeTemplate")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Thanks in advance for any ideas/suggestions.
Although I don't know the reason, it works:
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
Here is the whole code:
class MyViewController: NSViewController {
private lazy var searchField: NSSearchField = {
let searchField = NSSearchField(string: "")
if let searchButtonCell = searchField.searchButtonCell {
searchButtonCell.setButtonType(.toggle)
let filterImage = #imageLiteral(resourceName: "filter")
searchButtonCell.image = filterImage.tinted(with: .systemGray)
searchButtonCell.alternateImage = filterImage.tinted(with: .systemBlue)
}
searchField.focusRingType = .none
searchField.bezelStyle = .roundedBezel
searchField.delegate = self
return searchField
}()
...
}
extension MyViewController: NSSearchFieldDelegate {
func searchFieldDidStartSearching(_ sender: NSSearchField) {
sender.searchable = true
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
sender.searchable = false
}
}
extension NSSearchField {
var searchButtonCell: NSButtonCell? {
(self.cell as? NSSearchFieldCell)?.searchButtonCell
}
var searchable: Bool {
get {
self.searchButtonCell?.state == .on
}
set {
self.searchButtonCell?.state = newValue ? .on : .off
self.refreshSearchIcon()
}
}
private func refreshSearchIcon() {
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
}
}
extension NSImage {
func tinted(with color: NSColor) -> NSImage? {
guard let image = self.copy() as? NSImage else { return nil }
image.lockFocus()
color.set()
NSRect(origin: NSZeroPoint, size: self.size).fill(using: .sourceAtop)
image.unlockFocus()
image.isTemplate = false
return image
}
}
I was having the same issue. A simple override fixed this issue for me
extension NSSearchField{
open override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
}
As you can see when you click inside the view it's still focussed on the search text field(as you can still type in it after you clicked underneath it). Since the change image is on when it loses focus, you should check if you clicked outside of the text field.
Solve problem by subclassing NSSearchFieldCell and assign this class to field's cell.
You don't even need to subclass NSSearchFieldCell.
When you create your NSSearchField from code, you can do something like this:
if let searchFieldCell = searchField.cell as? NSSearchFieldCell {
let image = NSImage(named: "YourImageName")
searchFieldCell.searchButtonCell?.image = image
searchFieldCell.searchButtonCell?.alternateImage = image // Optionally
}
If you're using storyboards, you can do the same in didSet of your #IBOutlet.

Why delegate method is not called?

I am trying to notify ChatViewController that a chat was deleted in MessagesViewController using a protocol, but the delegate method implemented in ChatViewController is never called.
In the navigationController hierarchy ChatViewController is on top of MessagesViewController.
protocol MessagesViewControllerDelegate:class {
func chatWasDeletedFromDatabase(chatUID: String)
}
class MessagesViewController: UITableViewController {
weak var delegate: MessagesViewControllerDelegate?
func observeChatRemoved() {
print("it is gonna be called")
//inform ChatViewController that a chat was deleted.
self.delegate?.chatWasDeletedFromDatabase(chatUID: chat.chatUID)
print("was called here") //prints as expected
}
}
class ChatViewController: JSQMessagesViewController {
var messagesVC: MessagesViewController?
override func viewDidLoad() {
super.viewDidLoad()
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
}
}
extension ChatViewController: MessagesViewControllerDelegate {
func chatWasDeletedFromDatabase(chatUID: String) {
print("chatWasDeletedFromDatabase called") //never prints out
if self.chatSelected.chatUID == chatUID {
//popToRootViewController
}
}
It seems
weak var delegate: MessagesViewControllerDelegate?
is nil you have to set it to the ChatViewController presented instance what ever how you present it
let chat = ///
self.delegate = chat
self.navigationController?.pushViewController(chat,animated:true)
Also do
chat.messagesVC = self
as this
messagesVC = storyboard?.instantiateViewController(withIdentifier: "MessagesViewController") as! MessagesViewController
messagesVC?.delegate = self
isn't the currently presented messagesVC , so comment the above 2 lines

Protocol being reset in cell

I have a cell with a textField and a button. The button opens a page to collect data and has a protocol to pass that data back to the cell and fill the textField. That all works fine, however, when I come back the value is reset to zero. Print statements show that it is passing the data when it's set, but hitting Back clears it for some reason.
Protocol
protocol DistanceProtocol {
func distanceSet(distance: Double)
}
Call to protocol method
distanceProtocol?.distanceSet(totalDistance)
Cell class
class InputCell: CalculatorCell, DistanceProtocol {
#IBOutlet var textField: UITextField?
private var inputType = InputType.undefined
var viewController = UIViewController()
override func getHeight() -> CGFloat {
return 90
}
func distanceSet(distance: Double) {
print(distance)
textField?.text = "\(distance)"
}
func getInputType() -> InputType {
return inputType
}
func setInputType(inputType: InputType) {
self.inputType = inputType
}
#IBAction func walkTouched(sender: UIButton) {
let mapVc = viewController.storyboard!.instantiateViewControllerWithIdentifier("Map") as! MapLocationsViewController
mapVc.distanceProtocol = self
viewController.navigationController?.pushViewController(mapVc, animated: true)
}
}
As far as I can tell, everything is set up correctly. It's not reloading the cells in the tableView when I come back. Why is it resetting/how can I prevent it?
the text field is only set when you call the function. Unless you call this function in cellForRowAtIndex path it wont retain the value
I would likely implement it using a setter, do everytime the value is set, the label gets updated
protocol DistanceProtocol {
func distanceSet(distance: Double)
}
class CellWithText: UITableViewCell {
var cellText: String {
didSet {
textLabel?.text = cellText
}
}
}
extension CellWithText: DistanceProtocol {
func distanceSet(distance: Double) {
self.cellText = "\(distance)"
}
}
Then in your cellForRowAtIndexPath call you would call the function
cell.distanceSet(19.0)
i think you might try singleton design pattern or you can use struct for copying.

Swift function textfield got focus OSX

Currently I am having multiple textfields in a view. If the user taps at one of them there should be a function responding to the event. Is there a way on how to do react (if a textfield got the focus)? I tried it with the NSTextFieldDelegate method but there is no appropriate function for this event.
This is how my code looks at the moment:
class ViewController: NSViewController, NSTextFieldDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let textField = NSTextField(frame: CGRectMake(10, 10, 37, 17))
textField.stringValue = "Label"
textField.bordered = false
textField.backgroundColor = NSColor.controlColor()
view.addSubview(textField)
textField.delegate = self
let textField2 = NSTextField(frame: CGRectMake(30, 30, 37, 17))
textField2.stringValue = "Label"
textField2.bordered = false
textField2.backgroundColor = NSColor.controlColor()
view.addSubview(textField2)
textField2.delegate = self
}
func control(control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool {
print("working") // this only works if the user enters a charakter
return true
}
}
The textShouldBeginEditing function only handles the event if the user tries to enter a character but this isn't what I want. It has to handle the event if he clicks on the textfield.
Any ideas, thanks a lot?
Edit
func myAction(sender: NSView)
{
print("aktuell: \(sender)")
currentObject = sender
}
This is the function I want to call.
1) Create a subclass of NSTextField.
import Cocoa
class MyTextField: NSTextField {
override func mouseDown(theEvent:NSEvent) {
let viewController:ViewController = ViewController()
viewController.textFieldClicked()
}
}
2) With Interface building, select the text field you want to have a focus on. Navigate to Custom Class on the right pane. Then set the class of the text field to the one you have just created.
3) The following is an example for ViewController.
import Cocoa
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
4) Adding text fields programmatically...
import Cocoa
class ViewController: NSViewController {
let myField:MyTextField = MyTextField()
override func viewDidLoad() {
super.viewDidLoad()
//let myField:MyTextField = MyTextField()
myField.setFrameOrigin(NSMakePoint(20,70))
myField.setFrameSize(NSMakeSize(120,22))
let textField:NSTextField = NSTextField()
textField.setFrameOrigin(NSMakePoint(20,40))
textField.setFrameSize(NSMakeSize(120,22))
self.view.addSubview(myField)
self.view.addSubview(textField)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func textFieldClicked() -> Void {
print("You've clicked on me!")
}
}
I know it’s been answered some while ago but I did eventually find this solution for macOS in Swift 3 (it doesn’t work for Swift 4 unfortunately) which notifies when a textfield is clicked inside (and for each key stroke).
Add this delegate to your class:-
NSTextFieldDelegate
In viewDidLoad() add these:-
imputTextField.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: Notification.Name.NSTextViewDidChangeSelection, object: nil)
Then add this function:-
func textDidChange(_ notification: Notification) {
print("Its come here textDidChange")
guard (notification.object as? NSTextView) != nil else { return }
let numberOfCharatersInTextfield: Int = textFieldCell.accessibilityNumberOfCharacters()
print("numberOfCharatersInTextfield = \(numberOfCharatersInTextfield)")
}
Hope this helps others.