Change search field's icon - swift

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.

Related

SwiftUI Representable TextField changes Font on disappear

I am using a Representable ViewController for a custom NSTextField in SwiftUI for MacOS. I am applying a customized, increased font size for that TextField. That works fine, I had a question regarding that topic in my last question here.
I am setting the Font of the TextField in my viewDidAppear() method now, which works fine. I can see a bigger font. The problem is now, when my view is not being in focus, the text size is shrinking back to normal. When I go back and activate the window again, I see the small font size. When I type again, it gets refreshed and I see the correct font size.
That is my code I am using:
class AddTextFieldController: NSViewController {
#Binding var text: String
let textField = NSTextField()
var isFirstResponder : Bool = true
init(text: Binding<String>, isFirstResponder : Bool = true) {
self._text = text
textField.font = NSFont.userFont(ofSize: 16.5)
super.init(nibName: nil, bundle: nil)
NSLog("init")
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
textField.delegate = self
//textField.font = NSFont.userFont(ofSize: 16.5)
self.view = textField
}
override func viewDidAppear() {
self.view.window?.makeFirstResponder(self.view)
textField.font = NSFont.userFont(ofSize: 16.5)
}
}
extension AddTextFieldController: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
if let textField = obj.object as? NSTextField {
self.text = textField.stringValue
}
textField.font = NSFont.userFont(ofSize: 16.5)
}
}
And this is the representable:
struct AddTextFieldRepresentable: NSViewControllerRepresentable {
#Binding var text: String
func makeNSViewController(
context: NSViewControllerRepresentableContext<AddTextFieldRepresentable>
) -> AddTextFieldController {
return AddTextFieldController(text: $text)
}
func updateNSViewController(
_ nsViewController: AddTextFieldController,
context: NSViewControllerRepresentableContext<AddTextFieldRepresentable>
) {
}
}
Here is a demo:
I thought about using Notification when the view is coming back, however not sure
NotificationCenter.default.addObserver(self, selector: #selector(willEnterForeground), name: NSApplication.willBecomeActiveNotification, object: nil)
Here is a solution. Tested with Xcode 11.4 / macOS 10.15.4
class AddTextFieldController: NSViewController {
#Binding var text: String
let textField = MyTextField() // << overridden field
and required custom classes (both!!) for cell & control (in all other places just remove user font usage as not needed any more):
class MyTextFieldCell: NSTextFieldCell {
override var font: NSFont? {
get {
return super.font
}
set {
// system tries to reset font to default several
// times (here!), so don't allow that
super.font = NSFont.userFont(ofSize: 16.5)
}
}
}
class MyTextField: NSTextField {
override class var cellClass: AnyClass? {
get { MyTextFieldCell.self }
set {}
}
override var font: NSFont? {
get {
return super.font
}
set {
// system tries to reset font to default several
// times (and here!!), so don't allow that
super.font = NSFont.userFont(ofSize: 16.5)
}
}
}

Problem saving text from UITextView into Realm (Swift 5)

Trying to save the user's textView.text into Realm. Figured if I call my save function into the textFieldDidEndEditing function, it would trigger my Realm database to save it. Function runs ok (prints "Saved Successfully" when I end editing), but when I close out and come back, none of the data is there.
import UIKit
import RealmSwift
class NoteViewController: UIViewController, UITextViewDelegate {
var textView = UITextView()
var notes: Results<Notes>?
let realm = try! Realm()
var selectedNote: Menu? {
didSet {
loadNotes()
}
}
#IBOutlet weak var theTextView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
loadNotes()
self.textView.delegate = self
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
override func viewDidDisappear(_ animated: Bool) {
saveNote()
}
func textViewDidBeginEditing(_ textView: UITextView) {
}
func textViewDidEndEditing(_ textView: UITextView) {
saveNote()
}
//MARK: - Data Manipulation
func loadNotes() {
notes = realm.objects(Notes.self)
textView.reloadInputViews()
}
func saveNote() {
if let currentNote = self.selectedNote {
do {
try self.realm.write {
let newNote = Notes()
newNote.body = theTextView.text!
newNote.dateCreated = Date()
currentNote.notes.append(newNote)
print("Saved successfully")
}
} catch {
print("Error saving note body, with \(error)")
}
}
}
}
Realm File:
class Notes: Object {
#objc dynamic var body: String = ""
#objc dynamic var dateCreated: Date?
var parent = LinkingObjects(fromType: Menu.self, property: "notes")
}
Menu Realm File:
class Menu: Object {
#objc dynamic var name: String = ""
#objc dynamic var preview: String = ""
let notes = List<Notes>()
}
'''
It looks like you are using a storyboard to create your layout.
Get rid of var textView = UITextView() and textView.reloadInputViews() then.
Replace self.textView.delegate = self with theTextView.delegate = self in viewDidLoad().
To check the content of theTextView modify your print statement:
print("Saved successfully: \(theTextView.text!)")
If there is no text printed right after "Saved successfully: " then check Interface builder for theTextView: verify the outlet of theTextView and that this is actually the UITextView you are using (since your app does print something it looks like it is hooked up somehow because otherwise it would crash in newNote.body = theTextView.text!).
I think you also forgot to actually initialise theTextView with notes.body.
Try to modify loadNotes() like this:
func loadNotes() {
notes = realm.objects(Notes.self)
theTextView.text = notes.body
}

iCarousel shows images only after resize

I have imported the iCarousel provided by nicklockwood into a macOs app.
https://github.com/nicklockwood/iCarousel
The app is written in Swift.
After managing to get everything working (importing .m and .h files, adding Bridging Header) there is one minor thing.
Once the app is started and the NSViewController is activated I see a blank ViewController. Only if I start resizing the view I see the iCarousel with all loaded picture.
My code looks like the following:
class CarouselViewController: NSViewController, iCarouselDataSource, iCarouselDelegate {
var images = [NSImage]()
#IBOutlet var carousel: iCarousel!
override func awakeFromNib() {
super.awakeFromNib()
loadImages()
}
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.type = iCarouselType.coverFlow
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
}
func loadImages() {
let filePath = "/pics/"
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.picturesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).first
let path = paths?.appending(filePath)
//
let fileManager = FileManager.default
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath: path!)!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("jpg") || element.hasSuffix("png") {
if let image = NSImage(contentsOfFile: path! + element) {
print("File: \(path!)\(element)")
self.images.append(image)
}
}
}
}
func numberOfItems(in carousel: iCarousel) -> Int {
return images.count
}
func carousel(_ carousel: iCarousel, viewForItemAt index: Int, reusing view: NSView?) -> NSView {
let imageView = NSImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
imageView.image = self.images[index]
imageView.imageScaling = .scaleAxesIndependently
return imageView
}
func carouselItemWidth(_ carousel: iCarousel) -> CGFloat {
return 200.0
}
}
How can I manage to display my iCarousel without resizing first?
Thank you
I guess I've found one solution:
The drawing of the actual images in the carousel is triggered by layOutItemViews or layoutSubviews. One function that is public accessible is setType which allows to set the carousel type.
If I set the type after assigning dataSource and after loading the data, the images will be displayed fine.
So the most simple answer is to change viewDidLayout like the following:
override func viewDidLoad() {
super.viewDidLoad()
self.carousel.dataSource = self
self.carousel.delegate = self
carousel.reloadData()
// Triggers layOutItemView and thus will render all images.
self.carousel.type = iCarouselType.coverFlow
}
Now it is also possible to assign datasource via storyboard and the Connection inspector.

Passing selector via IBInspectable in Swift 3

I created a custom control and I want to pass the action in an #IBInspectable property to achieve the same effect of setting up an #IBAction using UIButton. How should I go about doing this?
class MyControl: UIButton {
// Problem with this string approach is
//I have no way to know which instance to perform the selector on.
#IBInspectable var tapAction: String?
// set up tap gesture
...
func labelPressed(_ sender: UIGestureRecognizer) {
if let tapAction = tapAction {
// How should I convert the string in tapAction into a selector here?
//I also want to pass an argument to this selector.
}
}
}
I really don't know why do you want it, but... Here is my solution:
Create a MyActions class, with actions that MyControl can to call:
class MyActions: NSObject {
func foo() {
print("foo")
}
func bar() {
print("bar")
}
func baz() {
print("baz")
}
}
Replace your MyControl class to
class MyControl: UIButton {
#IBInspectable var actionClass: String?
#IBInspectable var tapAction: String?
private var actions: NSObject?
override func awakeFromNib() {
// initialize actions class
let bundleName = Bundle.main.bundleIdentifier!.components(separatedBy: ".").last!
let className = "\(bundleName).\(actionClass!)"
guard let targetClass = NSClassFromString(className) as? NSObject.Type else {
print("Class \(className) not found!")
return
}
self.actions = targetClass.init()
// create tap gesture
let tap = UITapGestureRecognizer(target: self, action: #selector(pressed(_:)))
self.addGestureRecognizer(tap)
}
func pressed(_ sender: UIGestureRecognizer) {
actions!.perform(Selector(tapAction!))
}
}
And, set attributes of your button:
You can change the Tap Action in run time, for example:
#IBAction func buttonChangeAction(_ sender: Any) {
buttonMyControl.tapAction = textField.text!
}
Maybe you can change my code to pass parameters, but... it's you want?

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.