How to make key shortcut groups in ios/catalyst - keyboard-shortcuts

In my app, I am using UIViewController.addKeyCommand() to create keyboard shortcuts. When user holds CMD, a nice table of all commands available is automatically shown. However in iOS 15, these table can be ordered into groups. At least system tables are:
(groups System, Multitasking, Split view, Slide over). Is it possible to do this grouping also in my app? If yes, how?

Got it working, but it needs different mechanism then UIViewController.addKeyCommand()
In AppDelegate, override func buildMenu()
Inside, create one or more UIMenu objects, and populate their children property with UIKeyCommandObjects .
Insert menus in builder using builder.insertSibling() or similar.
That's all. Every UIKeyCommand thus added to menu will work as if it was added by UIViewController.addKeyCommand(). And every UIMenu will work as a group of commands, where UIMenu.title property is the title of the group.
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
if (builder.system != .main) {
return
}
let cmd = UIKeyCommand(title: "TestCmd", action: #selector(MainViewController.ref!.actTest(sender:)), input: "x", discoverabilityTitle: "Test command")
let menu = UIMenu(title: NSLocalizedString("Test group", comment: "hotkey group name"),
image: nil, identifier: UIMenu.Identifier(rawValue: "myapp.menu.ios.test"),
children: [cmd])
builder.insertSibling(menu, afterMenu: .view)
}

Related

How to switch to another XML layout after click on button?

I would like to have one kotlin file with the logic and I would like to allow users to switch between two different XLM layouts (logic of program is still the same, but layout of buttons shall be changed when clicking on button).
I simply add setContentView function to setOnClickListener for this button in order to load activity_main_second_layout.xml layout.
PS. activity_main_second_layout.xml is almost the same like activity_main.xml, I only changed the position of elements (not the names of elements)
button_switch_to_the_second_design.setOnClickListener {
setContentView(R.layout.activity_main_second_layout);
}
When clicking on the button, voala, the layout really changes to the second one.
BUT the functionality of the program is not working any more, the logic disappear. It seems that I need to resume running of the program somehow to make the code working again without interuption including loss of variables.
There is a lot of ways to do that.
In my opinion you should not try to change layout in runtime - it's possible, but you have to override setContentView and rebind all views and all listeners (or do it in other method, which will be called after changing the layout).
So... Sth like this:
fun sth() {
setContentView(R.layout.activity_main_second_layout)
rebindLayout(R.layout.activity_main_second_layout)
}
fun rebindLayout(#LayoutRes layoutId: Int) {
when (layoutId) {
R.layout.activity_main_first_layout -> { /* rebind views here */ }
R.layout.activity_main_second_layout -> { /* rebind views here */ }
}
}
The other's, better I think is to create independent fragments and change fragment via fragmentManager.
Others approches - ViewAnimator, ViewSwitcher.

Disable NSDocument's "Revert To" & "Duplicate" menu item

I am creating a Mac app to read a XML document and save it. Everything is working fine except "Revert To" & "Duplicate" menu items. Till i find a solution for that i want to disable both of them, but i didn't found any solution for it, Please let me know how can i disable both the options so that they end user cannot click on them.
I already looked into Menu's from .xib so that i can disable them but i don't see any options.
I tried to somehow manipulate below code, but i didn't found any answers.
override func duplicate() throws -> NSDocument {
return self
}
The general way to disable a menu item in Cocoa is returning false in validateMenuItem(_:) (or validateUserInterfaceItem(_:).)
In this case, put the following code in your NSDocument subclass.
override func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
guard let action = menuItem.action else { return false }
switch action {
case #selector(duplicate(_:)):
return false
case #selector(revertToSaved(_:)):
return false
default: break
}
return super.validateMenuItem(menuItem)
}
However, according to the Apple's Human Interface Guidelines, you should not leave menu items which are not used. So, if your app doesn't support duplication and revert features at all, I prefer to remove the items rather than to disable.

Trigger buttons in Touch Bar when sliding

I have created a Touch Bar with many colourful buttons.
The user will often change the color using the buttons.
When a button is pressed some graphical elements of the current window change accordingly with the color of the button.
I would like that sliding the finger among the buttons the elements will change their color, without having to wait for a "touch up inside" event.
Is there a quick way to do that?
EDIT: I think that a way to do that would be use a button of type momentaryPushIn or to create a class subclassing a button and handing something like a "touch inside" event.
I would prefer the first method but I am finding a very poor documentation.
Since you approved Scrubber solution here it is:
Firstly you need to screate instance of NSScrubber and insert it into your touch bar. Make sure that selection mode will be fixed.
yourScrubber.mode = .fixed
Then for purposes of highlighting(just to see outline be eyes, without this outline showed everything will work correctly) set Selection Overlay Style to .outlineOverlay.
youtScrubber.mode = .selectionBackgroundStyle = .outlineOverlay
Next set delegates for data source and for selection events.
yourScrubber.delegate = self
yourScrubber.dataSource = self
Then return amount of items in scrubber
public func numberOfItems(for scrubber: NSScrubber) -> Int {
return 8
}
Now you need to insert items(NSScrubberItemView). Which can be also filled with NSScrubberImageItemView or NSScrubberTextItemView or even your custom NSView.
There are many ways how to fill content of scrubber, for easy case where you will provide images it will look like this:
public func scrubber(_ scrubber: NSScrubber, viewForItemAt index: Int) -> NSScrubberItemView {
let itemView = scrubber.makeItem(withIdentifier: "colorIdentifier", owner: nil) as! NSScrubberImageItemView
itemView.image = yourImage
return itemView
}
Now you should be able to see something like this: (I used random images for my scrubber)
Also you should be able to visible(if you enabled outline) select items:
Ok and now the most important part, how to get the effect you desire: (use of delegate protocol method)
public func scrubber(_ scrubber: NSScrubber, didHighlightItemAt highlightedIndex: Int) {
print("highlight = \(highlightedIndex)")
//your code here
}
Which results to something like this: (notice output window)
You can also add spacing between cells be defining cell width:
func scrubber(_ scrubber: NSScrubber, layout: NSScrubberFlowLayout, sizeForItemAt itemIndex: Int) -> NSSize {
return NSSize(width: yourWidth, height: 30)
}
Also maybe you will find use for:
optional public func scrubber(_ scrubber: NSScrubber, didSelectItemAt selectedIndex: Int)

In TornadoFX, how can I separate layouts to different classes and then use them in builder?

For example, I want to have a TabPane, but I want to have tabs each in its separate class. Is there a way to make this work with the builder? I want to do something like this:
tabpane {
MyFirstTab()
MySecondTab()
etc.
}
On a general basis you add the root node from another View with the add command:
add(SomeView::class)
You can also inject a View and add it:
val someView: SomeView by inject()
override val root: borderpane {
center {
add(someView)
}
}
add is the same as doing this += someView. What happens here is that the framework find the root node of the View and appends it to the children property of the parent Node. It also knows about special containers like the BorderPane, so it does the right thing when you add something inside the center builder etc.
The TabPane however, takes Tab instances, which are not nodes. You need to add the tab using the tab builder and assign some content to it. The builders are smart enought to understand that if you do add inside a Tab, it should assign to the content property of the Tab. Therefore you can write:
tab("My First Tab") {
add(MyFirstTab::class)
}
Or if you already have an instance of the content you'd like to assign:
tab("My First Tab") {
add(myFirstTab)
}
The MyFirstTab class must be a View or Fragment.

How can I set the default state of a UISegmentedControl?

Is there a way to set the starting selected segment in a UISegmentedControl in Interface Builder, or do I have to do it in the code? If it's in the code, is viewDidLoad the best place to set it?
From code, you can do:
self.segmentedControl.selectedSegmentIndex = someDefaultIndex
Whether you should set it in viewDidLoad: or not depends entirely on the structure of your application. For example, if your app is starting up and loading the view for the first time and needs to set the control to whatever value it had during the previous run of the app, then it definitely makes sense to do it there.
In Interface Builder when you select UISegmentedControl object on your UI, then in attributes pane, in segment control there's segment drop down menu, select segment that you want selected (0,1 and so on) and tick the 'selected' option below it.
Select default value for your UISegmentedControl via storyBoard
Open ur storyboard and select the UISegmentedControl
Select atributes inspector of your UISegmentedControl (in the right corner)
Search the 'segment' atribute and open de dropdown.
Select the item you want to be selected by default.
Mark the selected checkbox to set selected by default.
If you don't use storyboards and want to set a default index after some setup/networking like me, this little snippet will select something if the user hasn't. I placed this in my subclass of UISegmentedControl, but you could place this anywhere. (Swift 3)
Decl: var UISegmentedControlNoSegment: Int { get }
Desc: A segment index value indicating that there is no selected segment. See selectedSegmentIndex for further information.
Short version:
if selectedSegmentIndex == UISegmentedControlNoSegment {
selectedSegmentIndex = initialIndex
}
Longer version
func reloadData() {
guard let numberOfItems = dataSource?.numberOfItems() else {
return
}
removeAllSegments()
for index in 0...numberOfItems {
insertSegment(with: $image, at: index, animated: false)
}
if selectedSegmentIndex == UISegmentedControlNoSegment {
selectedSegmentIndex = initialIndex
}
}
After clicking the Segmented Control go to where you created the segments and choose the one you want be default. Then below that there will be a Box with "Selected" by it. Select that and it will be default.