How to use NSTextFinder with NSOutlineView? - swift

There is not much information about how to use NSTextFinder and most of them are about NSTextView. I am interested to learn how to use it with NSOutlineView. This is what I have done.
On storyboard, I dragged an NSTextFinder onto a view controller where it contains an NSOutlineView. I connected the client to the view controller and the find bar container to the NSScrollView that encloses the NSOutlineView. I made an #IBOutlet to the NSTextFinder and enabled incremental searching and dim content view.
My code looks something like this.
class ViewController: NSViewController {
#IBOutlet var outlineView: NSOutlineView!
#IBOutlet var textFinder: NSTextFinder!
// ...
#IBAction func findSomething(_ sender: Any?) {
print("findSomething(_:) was called!")
textFinder.performAction(.showFindInterface)
}
}
I connected findSomething(_:) as a first responder to one of the NSMenuItem. The method is called and a find bar actually appear.
How do I actually get it to search strings in the NSOutlineView? I am not interested in find-and-replace. I just want it like Safari find bar.
Apple documentation on NSTextFinder is very hard to understand and there is no example code I could look into.

willeke is correct: Make your viewController, which owns the NSOutlineView, the client of the NSTextFinder and implement the NSTextFinderClient protocol. For an outline view, you implement the methods func stringLength() -> Int and func string(at: Int, effectiveRange: NSRangePointer, endsWithSearchBoundary: UnsafeMutablePointer<ObjCBool>) -> String - you want to give the NSTextFinder the illusion that there's one giant string, broken into pieces.
Example when it asks for the piece that contains location 512, you return the string from 500 to 600, and store NSMakeRange(500, 100) into its effecive range argument.
When it actually finds something, it will call your selectedRanges setter for you to move the selection there.
To make this work. You'll need to do some work up front: to provide the illusion that your document is one giant range, you'll need to build a data structure that lets you map a range from NSTextFinder to a piece of your data.

Related

How to share a builder class across multiple instances in swift

I am trying to become a better developer and just started to study and use software patterns in small test projects.
In my current project I focused on the builder pattern.
Basically all I have is an App with two Views (Storyboards), A single ViewController-Class (used by all of the views) and one Builder-Class that creates an object with two properties.
The Builder-Class is implemented in the ViewController.
class ViewController: UIViewController {
#IBOutlet var startTimePicker: UIDatePicker!
#IBOutlet var workingHoursPicker: UIDatePicker!
#IBAction func setStartTimeButtonPressed(_ sender: Any) {
setStartTime()
}
#IBAction func setWorkingHoursButtonPressed(_ sender: Any) {
setWorkingHours()
}
var workdayBuilder: WorkdayBuilder = ConcreteWorkdayBuilder()
....
My current problem is that every time I navigate to the next View (using a Segue) the ViewController will be instantiated again and loses the value I set in the previous view.
I know that I could technically just pass the value to the next ViewController and then set it there or that I could set the value and then pass the instance of my builder to the next view. But that would kind of destroy the purpose of the builder in this project IMO.
I then thought that I could wrap my Builder into a Singleton and use it like a shared instance across the views:
final class WorkdayBuilderSingleton {
static let sharedInstance: WorkdayBuilder = ConcreteWorkdayBuilder()
init() {}
}
But this lead to another error in my VC when I tried to set a property. "Cannot assign to property: 'sharedInstance' is a 'let' constant".
So I am left with some questions and hope you can help me.
Is it a good or OK practice in general to make a builder a singleton or did I make a bad mistake here?
If it is OK, can I change mit static let into a static var or would that kill the singleton?

How do you get a text field input in you view controller code?

I’m trying to make Xcode print "Nice!" when you type in "Hi". I've used a IBOutlet, but I don’t know how to use the user input in my code. Also BTW I'm using Storyboard and not SwiftUI. It also gives me an error when I try to compare the datatype UIViewController and a String. Here is my view controller code(with the default App Delegate and Scene Delegate code):
import UIKit
class ViewController: UIViewController {
#IBOutlet var yeet: [UITextField]!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
func fuel(_ yeet:UIViewController) -> Int {
if yeet == ("hi") {
print("Nice!")
}
}
}
your textfield show be setup as
#IBOutlet weak var textFeildName: UITextField!
you will need to change a couple things inside of your file to prevent a crash. I'd delete the textfield and drag it into the assistant view and give it a new name.
but before you press "connect" press the "outlet" tab and change it to "Action" and then a new selector should come up select "Editing Did End" and go to the top and press "Did End On Exit"
after that is done would want to reference the variable of the text field:
example:
#IBAction func TextFieldName(_ sender: Any) {
if(self.TextFeildName.text!.contains("hi")){
print("Nice!")
}
}
On top of all this, you do not compare strings with == that's only if you compare 2 separate strings for example stringOne == stringTwo if you are comparing or asking if a string contains anything you'd want to use the developing language specific string container IE: .contains
Also, please do not include "Xcode" as a tag with your question, as that should be reserved for Xcode related problems. not Swift or objective-c coding issues.

Making ViewController slimmer by moving TableView away

I am quite new into programming and facing some issues while trying to slim down my ViewController by moving creation of the tableView and associated views to the separate class and moving delegates and datasource from VC to separate one.
My constellation of current files and work is as follows:
After network connection, when data are received, I am calling inside a closure class to set view which will have embedded UITableView.
Fetched data are being saved into the CoreData stack
I instantiate view from another class
var detailView: DetailView! { return self.view as? DetailView }
Once I will download first part of the UI (separate call which works fine)
I am moving onto the part which is messy and surpass my abilities
I call a function createReposCard()
dispatchGroup.notify(queue: dispatchQueue) {
DispatchQueue.main.async {
// slide animation
UIView.animate(withDuration: 0.75, delay: 0.5, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.0, options: [], animations: {
self.detailView.reposCard.center = CGPoint(x: self.detailView.reposCard.center.x, y: self.detailView.reposCard.center.y-UIScreen.main.bounds.height)
}, completion: nil)
self.detailView.createReposCard(for: self.repos)
self.detailView.detailsTableView.reloadData()
self.activityIndicator.stopAnimating()
self.activityIndicator.removeFromSuperview()
}
}
Code is incomplete to depict the problem only, but what it does one by one.
I am passing info about selected user (irrelevant info)
then I am making a reference to my DetailsViewController which still e.g. holds CoreData stack
In initializer I am instantiating detailsViewController and passing it onto the class which holds delegates (I am passing it to have there reference to the CoreData)
class DetailView: UIView {
var selectedUser: User?
var detailsViewController: DetailsViewController!
let detailsTableView: UITableView = {
let tableView = UITableView()
tableView.frame = CGRect.zero
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "reposCell")
return tableView
}()
init(selectedUser:User, frame: CGRect) {
super.init(frame: frame)
self.selectedUser = selectedUser
detailsViewController = DetailsViewController()
let tableViewDelegates = TableViewDelegates(detailsViewController: detailsViewController)
detailsTableView.delegate = tableViewDelegates
detailsTableView.dataSource = tableViewDelegates
}
And finally code jumps into the depicted class where I am knocked down by "Unexpectedly found nil while implicitly unwrapping..."
public class TableViewDelegates: NSObject, UITableViewDataSource,UITableViewDelegate {
private let detailsViewController: DetailsViewController
init(detailsViewController: DetailsViewController){
self.detailsViewController = detailsViewController
super.init()
}
public func numberOfSections(in tableView: UITableView) -> Int {
return detailsViewController.fetchedResultsController.sections?.count ?? 1
}
...
Actually I don't know if my concept is good as I feel quite lost, but my intentions were as follows:
Move View creation to other class
Move TableView delegates to other class
Finally - move networking and CoreData to other class.
But as I see, simple data passing is overcoming my abilities.
I think this question can be divided into two parts:
1) Why is my variable nil when I unwrap it?
I don't think we have enough information to answer this accurately, but my overall approach would be like this:
Check what nil variable is being unwrapped;
Make sure this variable is being properly initialized;
Make sure that the object is not being incorrectly deinitialized;
If you're using Storyboard, use the inspectors to check if everything is set correctly.
There's a particular observation about step 2: you should check the order of execution of your methods to make sure that the variable is properly initialized. Why am I emphasizing this? Because there's a chance that some view (e.g., detailView) is initialized like an ordinary UIView, and then you try to access an element that is not part of a UIView object (e.g., a table view). In other words, check if you're setting the custom views before you try to access them.
2) How to structure the project in a more organized way?
This is a more interesting question, and I think that choosing a better approach will help you to avoid issues like what you're experiencing. I will divide this into some topics. Everything here is my personal opinion and doesn't necessarily reflect the best approach, especially because "best" is subjective here.
PersistenceManager class
First, passing a reference of a view controller to another class just to access CoreData doesn't seem like a good option. A better approach would be to have a PersistenceManager class, for example. You could use an object of this class to fetch and save data. You could pass this object instead of the view controller.
In some architectures (e.g., VIPER), it wouldn't be correct for the view controller to access the persistence directly, so it would be more appropriate to pass an array of already fetched objects. For example:
class TableViewController {
private let tableView: UITableView!
private var currentlyDisplayedUsers: [Users]?
func displayUsers(_ users: [Users]) {
self.currentlyDisplayedUsers = users
self.tableView.reloadData()
}
}
In the example above, the tableView would display currentlyDisplayedUsers, which would be updated by the method displayUsers, which would be called by someone else, like a Presenter.
Network class
Second, I think you should have a network class to download data from the internet. You would use instances of the Network class in the application logic. In other words, you would have something like
// This is in the application logic
// In MVC, the logic is in the Controller, while in VIPER the logic is in the Interactor
class ApplicationLogic {
let networkAPI: Network?
...
func fetchUserData() {
networkAPI?.fetchUsers() { data, error in
// Update the UI based on the response
// Using the previous example, you could call displayUsers here
}
}
}
TableView, TableViewDelegate, and TableViewDataSource
Finally, how to organize these guys. UITableViewDelegate is responsible for telling us about events in the table, while UITableViewDataSource is responsible for filling the table with data, which means that both are strongly related to the table view itself. That said, imho, both should be implemented in different swift files, but as extensions of the view controller that has a reference to the table view. Something like
// TableViewController+UITableViewDelegate.swift
extension TableViewController: UITableViewDelegate {
...
}
// TableViewController+UITableViewDataSource.swift
extension TableViewController: UITableViewDataSource {
...
}
Using this approach, the delegate and the data source would have access to the users array, mentioned earlier. Here is an example of how to implement a similar approach using VIPER.
So, I hope I could provide a basic idea on these topics. If you want to understand more about how to structure your code, I suggest researching iOS design patterns and architectural patterns. Some architectural design patterns that are famous in iOS development are MVC, MVP, MVVM, and VIPER.

Cannot modify NSTabViewItem

I may be getting lost in a glass of water as I am not an experienced developer but I cannot seem to be able to implement a simple override to modify the size of an NSTabView item.
I have a Tab View Controller (Style = toolbar)
I have a Tabless Tab View
I have 3 Tab Items. For testing I have only subclassed one of them to the subclass below
I have created a new subclass of NSTabViewItem: MyTabViewItem and subclassed one of the 3 tab Items. The code is:
import Cocoa
class MyTabViewItem: NSTabViewItem {
override func drawLabel(_ shouldTruncateLabel: Bool, in labelRect: NSRect) {
var size = self.sizeOfLabel(false)
size.width = 180
print("Draw!!")
}
override func sizeOfLabel(_ computeMin: Bool) -> NSSize {
var size = super.sizeOfLabel(false)
size.width = 180
print("Draw!!")
return size
}
}
Everything works, except the subclassing. The Tabs appear, they do operate by switching the views and the program runs as it should. Except that it does not resize the Tab Item. The code in the subclass MyTabViewItem is never reached (it never prints Draw!! as it should.
I cannot understand what I am missing here. I have not read of any IB connection to make (and I cannot seem to be able to connect the Tab Items anyways). Please apologise if it isa trivial question but I have searched everywhere and not found anything to help me.
Thank you
You said:
I have a Tabless Tab View
This is your problem. An NSTabView only asks an NSTabViewItem to drawLabel if the NSTabView itself is responsible for drawing the tab bar, but you have a “Tabless” tab view. (“Tabless” is the default style when you drag an NSTabViewController into a storyboard.)
You also said:
I have a Tab View Controller (Style = toolbar)
So you don't even want the tab view to draw a tab bar; you want items in the window toolbar to select tabs (like in Xcode's preference window).
Your ability to customize the toolbar items created for your tabs is limited. You can subclass NSTabViewController and override toolbar:itemForItemIdentifier:willBeInsertedIntoToolbar:, like this:
override func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
let toolbarItem = super.toolbar(toolbar, itemForItemIdentifier: itemIdentifier, willBeInsertedIntoToolbar: flag)
if
let toolbarItem = toolbarItem,
let tabViewItem = tabViewItems.first(where: { ($0.identifier as? String) == itemIdentifier.rawValue })
{
toolbarItem.label = "\(tabViewItem.label) 😀"
}
return toolbarItem
}
But I found that making other changes didn't work well:
Setting toolbarItem.image didn't work well for me.
Setting toolbarItem.view made the item stop receiving clicks.
Note that the minSize and maxSize properties are only used if toolbarItem.view is set.
Your best bet is probably to manage the toolbar yourself, without trying to use NSTabViewController's support.
I have also subclassed the NSTabViewController as follows:
import Cocoa
class MyTabViewController: NSTabViewController {
#IBOutlet weak var TradingTabItem: MyTabViewItem!
override func viewDidLoad() {
super.viewDidLoad()
print("Loaded Tab View")
TradingTabItem.label = "New"
// Do view setup here.
}
}
What happens now is that the tab item in my subclass (the only one of the 3 I subclassed) does change its label string to New. However, even if I have added the item as an IBOutlet here, it still does not change seize (and the overridden sizeOfLabel function is not reached).

Get reference to current object

My goal is to safe a reference from the button, label or textfield inside a variable.
The problem is that I don't know on which control the user tapped.
I am having a simple application which looks like this:
The user can touch any control.
It is easy enough with just those three controls because I can drag in a action. But if I am having many of them I can't handle them all over the action methods. Is there a general way in which I can safe a reference to the control in a variable so that I can know which of the controls is the active one?
Edit
As suggested I am using a function and assigning the variable to the sender of the function. This is how it looks in code:
var currentObject: NSTextField!
override func viewDidLoad() {
super.viewDidLoad()
myTextfield.action = #selector(myAction)
}
func myAction(sender: NSTextField)
{
print("aktuell: \(sender)")
currentObject = sender
}
As you can see this only works for a NSTextfield. Is there a way in which the function works for every control?
Set the tag attribute for each item, and then you can check sender.tag to identify which object is calling it.
To set the tag, select the Attributes inspector in Storyboard (upper right side - middle button of Utilities) and look for this section: