How to get a search bar into an NSToolbar in a Catalyst app? - swift

I've seen some Catalyst apps add a search bar to the NSToolbar and was wondering how I could do the same. Would I have to import AppKit/Cocoa to get an NSSearchField and the actual NSToolbar? Or is there some way with UISearchBars that I am just not seeing? Any help would be great, thanks.
I have tried importing AppKit and Cocoa (Using a bundle loader), but I don't think I had done it right. If anyone has a solid guide on how to do this, please link it.
I also tried creating a UIBarButtonItem with a custom view of a UISearchBar, which doesn't end up rendering anything. (that can be found below)
This is found in a switch case on itemIdentifier.rawValue (in the toolbar itemForIdentifier function)
let search = UISearchBar()
search.sizeToFit()
let button = UIBarButtonItem(customView: search)
button.width = 100
let toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier, barButtonItem: button)
toolbarItem.isBordered = true
toolbarItem.isEnabled = true
return toolbarItem

Thanks to mmackh, the search bar can be added in just as easily as any other toolbar item. Thank all of you who looked into this.
https://github.com/mmackh/Catalyst-Helpers
Swift Example
Once you add the files to your project (and in this case your bridging header), just return this as you would any other toolbar item.
let item = NSToolbarItem_Catalyst.searchItem(withItemIdentifier: "searchBar", textDidChangeHandler: { string in
print(string)
})

I found a soulution that works for me, maybe it will be good enough for you too.
While I can't create a search bar in toolbar, I can create the button that looks pretty much as a search bar. That button then toggles the visibility of search bar in Catalyst application.
Here's the (obj-c) code for it:
UIImageSymbolConfiguration *conf = [UIImageSymbolConfiguration configurationWithPointSize:32 weight:UIImageSymbolWeightRegular];
UIImage *backImage = [UIImage systemImageNamed:#"magnifyingglass" withConfiguration:conf];
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithImage:backImage style:UIBarButtonItemStylePlain target:self action:#selector(searchButtonAction)];
NSToolbarItem *item = [NSToolbarItem itemWithItemIdentifier:itemIdentifier barButtonItem:buttonItem];
item.title = #"Search";
return item;
And here are the images of how this looks in app:
As a bonus, you could use some non trimmable space character (like this one) to add space after "Search" string, making the button look even more like a proper search bar.
Here's how that looks:

Related

Attempts to tap a UIButton in a UITableViewCell using UI Automation fail with "could not be tapped"

I have an iPhone app that I'm testing using UI Automation.
I have a button in a UITableViewCell but when I try to tap on it using UI Automation I get the following error.
Script threw an uncaught JavaScript error: target.frontMostApp().mainWindow().scrollViews()[0].elements()[element_name].tableViews()[0].elements().firstWithPredicate(name contains[c] 'Brooklyn').elements()["detailsButton"] could not be tapped
I have enabled accessibility on the button in Interface Builder and assigned the accessibility label (and identifier) "detailsButton". I can retrieve the button element and have verified that it is valid. I just can't tap it for some reason.
The UIButton is a round rectangular button with user interaction enabled. Thanks for any feedback.
You can alloc the UIButton except using round rect button. And also you can set target action of button in your controller class where table view delegate methods are presents.
Try asserting first if the button exists, use tuneup_js for easy assertions.
Then assert if the button is enabled.
Have you tried:
target.frontMostApp().mainWindow().scrollViews()[0].elements()[element_name].tableViews()[0].**cells()**.firstWithPredicate("name contains[c] 'Brooklyn'").elements()["detailsButton"]?
Also post another picture which shows the UIATableCell expanded where the detailsButton exists.
You have to use ButtonType=Custom
I don't know why you are using UI Automation. But as much I know about adding UIButton in a tableviewcell-
1) just add UIButton in UITableViewCell.
2) set UIButton's tag value and add target to that button.
3) In the target button method, you can get the UIButton object and do your stuff over there..
Hope this helps you out.
but=[UIButton buttonWithType:UIButtonTypeCustom];
You must use a custom button type.
I have met the same problem. After I added the delay it works.
UIATarget.localTarget().delay(1);
You can dispatch a tap event over the entire window:
function tap_from_window(elem) {
var mainWindow = target.frontMostApp().mainWindow();
var elemRect = elem.rect();
var windowRect = mainWindow.rect();
var xPos = (20 + elemRect.origin.x) / windowRect.size.width;
var yPos = (50 + elemRect.origin.y) / windowRect.size.height;
mainWindow.tapWithOptions({tapOffset:{x:xPos, y:yPos}});
}
var target = UIATarget.localTarget();
var window = target.frontMostApp().mainWindow();
var tableView = window.scrollViews()[0].elements()[element_name].tableViews()[0];
tap_from_window(tableView.elements().firstWithPredicate(name contains[c] 'Brooklyn'))
Like many things related to iOS, I have no idea why the element cannot be tapped directly. I adapted the above function from the code in this post: scrollToVisible error while testing on a device, UIAutomation

Access switch and textfield in tableview from another method

I'm having a tableview with a couple of cells with 4 uiswitches and one textfield inside. Simple question. I need a way to read all the positions of the switches and the string inside the textview by clicking a button. Tableview, textfield, switches and button is working fine. Just need to access the values of the switches and textfield from another method. By tagging the switches or something.
So how can I access the values from another method. Something like in tableview method:
...
UISwitch *switchView = [[option1 alloc] initWithFrame:CGRectZero];
switchView.tag = 3000;
cell.accessoryView = switchView;
...
And something like this in the button method:
...
BOOL status = [self.view viewWithTag:3000].on; //*Not a working method*
You are in the same class, nop ?
So maybe you could put your switch & co as class variable (declared in the header file). It would be easier ^^

Change title of UItabbaritem in other view?

How do I change the UITabbaritem titles when starting the App?
I got a UITabBar with 4 tabs. I want the user to be able to change between two different languages. If the user chooses a different language I need to set a different title to the UITabbaritems. I know I can use self.title = #"title"; but that only changes the current tabbaritem title. How can I change all the titles at once, on load, and when choosing a different language?
I got the same problem and solved it with.
[[self.tabBarController.viewControllers objectAtIndex:index] setTitle:#"Your title"];
Where index is your UITabBarItem index.
You need to store all your UITabBarItem into an array, when the user tap the button, you need to loop through that array and set the title.
for (UITabBarItem *item in items) {
item.title = #"WHATEVER HERE";
}

Force scope bar below UISearchBar

I have a UISearchBar and UISearchDisplayController, everything works great but my scope selector displays beside the text field instead of below it. I know that this is the expected action when the device is in landscape, but since I have the UISearchBar in the master view of a UISplitViewController it ends up looking like this:
Is there any way to force the scope bar to display below the text field in all interface orientations (I know that this works nicely in Mail.app on the iPad, so its possibly, but who knows if Apple decided to hide the option to do so)
This is not part of the public API. Hopefully it will be added in the future. It can be accomplished using Private APIs, looking through a UIKit class dump will help you there.
Good luck!
edit: Note that using private APIs can get you rejected from the app store, as most people already know.
I used the UISearchDisplayDelegate to show and hide the scope bar.
-(void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller
{
[controller.searchBar setShowsScopeBar:YES];
}
-(void)searchDisplayControllerWillEndSearch:(UISearchDisplayController *)controller
{
[controller.searchBar setShowsScopeBar:NO];
}
Don't use private APIs. Instead, follow Apple's recommendation from IOS User Interface Guidelines:
When a search bar is present, a scope bar can appear near it. The
scope bar displays below the search bar, regardless of orientation,
unless you use a search display controller in your code (for more
information on the way this works, see UISearchDisplayController Class
Reference). When you use a search display controller, the scope bar is
displayed within the search bar to the right of the search field when
the device is in landscape orientation (in portrait orientation, it’s
below the search bar).
This worked for me using storyboards.
Why not just set up your search bar programattically? Here's what I'm using on my current project. It hides the search bar until the user scrolls up, and the scope bar is loaded below the text field when they start typing items. From there just filter as usual:
- (void)setupSearchBar {
self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44)];
self.searchBar.showsScopeBar = YES;
self.searchBar.scopeButtonTitles = [NSArray arrayWithObjects:#"Users", #"Groups", nil];
self.tableView.tableHeaderView = self.searchBar;
CGPoint offset = CGPointMake(0, self.searchBar.frame.size.height);
self.tableView.contentOffset = offset;
self.searchController = [[UISearchDisplayController alloc] initWithSearchBar:self.searchBar
contentsController:self];
self.searchController.searchResultsDataSource = self;
self.searchController.searchResultsDelegate = self;
self.searchController.delegate = self;
}
call [self setupSearchBar]; in viewDidLoad you're off to the races.

How to programmatically replace UIToolBar items built in IB

I have a toolbar with various image buttons, created in Interface Builder.
I'd like to be able to programmatically replace one of the buttons with an activity indicator when pressed, and then put back the original button but change its color from white to yellow when the activity completes.
Is this possible with an IB built toolbar or must I look at building the whole toolbar programmatically and custom views?
Here is an example of what I did in a similar situation. I wanted to build the toolbar using Interface Builder but toggle one of the BarButtonItems based on whether or not it was "checked". In this example, there are a few key things to note. The following 2 member variables are defined for the class in the header file:
NSMutableArray *toolbarItems;
IBOutlet UIToolbar *toolbar;
NSUInteger checkUncheckIndex;
When I want to update the checked status, I call this function... Please note that there is a selector defined called checkUncheckClicked that is called when the particular button in the UIToolbar is clicked. And the UIToolbar is set up as an IBOutlet to toolbar. Alternately, you could hook up the UIBarButtonItem as an outlet itself and use that as your logic to identify the index of the button, or for that matter, you could hard-code the index of the button if things won't change over time. Finally, there is a checked.png and unchecked.png to alternate between that is included in the project.
- (void)updateBarButtonItemChecked:(BOOL)checked {
if (toolbarItems == nil) {
toolbarItems = [[NSMutableArray arrayWithArray:toolbar.items] retain];
checkUncheckIndex = -1;
for (NSUInteger i = 0; i < [toolbarItems count]; i++) {
UIBarButtonItem *barButtonItem = [toolbarItems objectAtIndex:i];
if (barButtonItem.action == #selector(checkUncheckClicked)) {
favoriteIndex = i;
break;
}
}
}
if (checkUncheckIndex != -1) {
UIBarButtonItem *barButtonItem = [[[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:checked ? #"checked.png" : #"unchecked.png"]
style:UIBarButtonItemStylePlain target:self action:#selector(checkUncheckClicked)] autorelease];
[toolbarItems replaceObjectAtIndex:checkUncheckIndex withObject:barButtonItem];
toolbar.items = toolbarItems;
}
}
And, of course toolbarItems and toolbar should be released in your dealloc for the class.
Hope this helps!
Here is the approach I used
It seemed to be much simpler to manage the toolbar entirely programatically so ....
In your view controller declare 1 or more sets of UIBarButtonItem items as property items also declare and hookup the toolbar as a UIToolbar property. Also declare 1 or more arrays to hold the items.
In the implementation
In viewDidLoad alloc and set your UIBarButtonItems for example
playButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemPlay
target:self
action:#selector(handlePlayClick)];
Flexible buttons (for alignment etc) are declared like this
flexButton1 =[[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace
target:nil action:nil];
There are several initMethods to handle the different types of buttons toolbars support. All follow a syntax similar to the above. Worth noting is the target and action settings. Target: would normally be self, action is the name of the function that button should trigger.
After alloc'ng your UIBarButtons add them to an array using initWithObjects.
Then to assign the buttons to the toolbar you would call
[toolbar setItems:<array name>];
Dont forget to dealloc your UIBarButtons and arrays at the end of your code.
Hope this helps. If you need more code let me know.
Rich D.
Swift
Much has changed since these answers have been posted. Here is a Swift 2.0 solution.
It matters not how the original UIBarButtonItem you are replacing has been created programmatically. All you need is the UIToolbar outlet. To replace, say, the 2nd item from the left, do this:
var toolbarItems = self.toolbar.items
let newItem = UIBarButtonItem(barButtonSystemItem: .Play, target: self, action: "play")
toolbarItems![1] = newItem
self.toolbar.setItems(toolbarItems, animated: true)
Swift has a much easier way. In my case, I am switching the play button with the pause button every touch.
#IBAction func playandPause(sender: UIBarButtonItem) { // Here we are creating an outlet. Hook this up to the bar button item.
for (index, button) in toolbarItems!.enumerate() { // Enumerating through all the buttons
if button === sender { // === operator is used to check if the two objects are the exact same instance in memory.
var barButtonItem: UIBarButtonItem!
if mediaplayer.playing {
mediaplayer.pause()
barButtonItem = UIBarButtonItem.init(barButtonSystemItem: .Play, target: self, action: #selector(playandPause(_:)))
}else {
mediaplayer.play()
barButtonItem = UIBarButtonItem.init(barButtonSystemItem: .Pause, target: self, action: #selector(playandPause(_:)))
}
toolbarItems![index] = barButtonItem // Replace the old item with the new item.
break // Break once we have found the button as it is unnecessary to complete the rest of the loop
}
}
}
I think it should be possible. You might either try creating an IBOutlet for that specific button in your ViewController class, and connect the button from IB to that outlet, or you can use the items property of that UIToolbar instance (you do have a reference to that, don't you?) and find the appropriate item in there, create a new NSArray with modified items, and set it back on that toolbar.items property.
HTH
A note on this - the toolbar will strip out any color in your icons, so you still won't be able to make it yellow. You'll need to change the image shape to indicate "on" instead.
Alternatively you'll need to load your BarButtonItems with UIButtons (use the initWithCustomView) and set the image of the button appropriately.
HTH
Try:
- (void)setTitle:(NSString *)title forItem:(int)item ofToolbar:(UIToolbar *)tb {
NSMutableArray *newItems = [tb.items mutableCopy];
UIBarButtonItem *old = [newItems objectAtIndex:1],
*titled = [[UIBarButtonItem alloc] initWithTitle:title style:old.style target:old.target action:old.action];
[newItems replaceObjectAtIndex:1 withObject:titled];
tb.items = newItems;
[titled release];
}
For the entire toolbar that you created in IB (that is to say, not the individual toolbar items), create an IBOutlet:
#IBOutlet weak var toolbarThatYouMade: UIToolbar!
This is an array of individual toolbar items, so you would access the leftmost member (for example) with a [0] index:
toolbarThatYouMade.items![0].image = UIImage(named: "New Image")
This code assumes that you have an image named "New Image" in your assets.
You can then trigger an image change for a toolbar item without having to access that specific item itself; for example, if you were using this for something like a media player, you could toggle pause/play images from:
a) the Pause/Play button itself,
b) the Stop button,
c) when you are notified of a change of player state,
or
d) something else entirely. (Be brave! Be bold! Be something else that begins with 'b'!)