Customising the sidebar with NSTintConfiguration - swift

In the Adopt the new look of macOS video, you can use NSTintConfiguration to customise the sidebar icon colour using this code.
func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? {
if case let sectionItem as SectionItem = item {
/*
This outline view uses a type called "SectionItem" to populate its top-level sections.
Here we choose a tint configuration based on a hypothetical `isSecondarySection` property on that type.
*/
return sectionItem.isSecondarySection ? .monochrome : .default
}
// For all other cases, a return value of `nil` indicates that the item should inherit a tint from its parent.
return nil
}
When I tried to force a colour on my app sidebar, let's say yellow for an example like this.
func outlineView(_ outlineView: NSOutlineView, tintConfigurationForItem item: Any) -> NSTintConfiguration? {
return .init(fixedColor: .systemYellow)
}
It (sort of) worked as intended, meaning that any rows that are not selected are customised as intended but when you select a row, the SF Symbols changes colour. I expected it to be yellow.
This is how it looks. You can see the icon tint colours are indeed yellow but only if you do not select them.
What I want is the highlighted row tint colour to be the same as the unselected rows. Like how Apple does it in their apps.

You’re seeing the green accent color row selection highlight (NSTableRowView.emphasized = true) and white icon because your outline view is currently the window’s first responder. If you select a different control outside of the outline view, like an NSTextField in the window’s main content area for example, the row selection will change to the inactive appearance (emphasized = false). This is independent of the new tint configuration API.
Mail and Finder go to some effort to prevent their sidebar outline views from becoming first responder in most situations, so they usually has the inactive appearance like in your second screenshot. You can approximate this behaviour by setting refusesFirstResponder = true on your outline view or by subclassing NSOutlineView and overriding acceptsFirstResponder if you need more fine-grained control.

Related

How to get rid of the white shadow in dark mode in macOS?

I am making a macOS app in Swift and am seeing a weird issue when I switch b/w light and dark modes. The shadow that appears around the image on the light mode is great but the one I am getting by default on the dark mode doesn't look nice. Does anyone know how to fix this? I am getting the same for all other UI elements like Checkboxes, Radio Buttons, etc.
Not nice
Nice
Note:
This image is added via the Interface Builder in Xcode.
I have the shadow checkbox unchecked as well. See below:
Make sure that the superviews of your view don't define a shadow either or that it has an opaque background.
If your superview doesn't have an opaque background, child views will also have a shadow.
You can either try implementing viewDidChangeEffectiveAppearance() on your NSView or do key-value observations on NSApp.effectiveAppearance. This way you'll be notified about appearance changes between light/dark mode so you can react accordingly.
You can detect whether a view is Dark Mode or not. And you hide shadow when in dark mode.
Add an extension to NSView:
extension NSView {
func isDarkMode() -> Bool {
if #available(OSX 10.14, *) {
return effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
}
return false
}
}
Create a custom view (view, button etc). Override viewDidChangeEffectiveAppearance method in your custom view and update layer.
class CustomView: NSView {
override func viewDidChangeEffectiveAppearance() {
// edit shadow's properties. ex: shadowOpacity.
if isDarkMode() {
// dark mode
} else {
// light mode
}
}
}

How to implement DarkMode Into app in Swift

sorry if that question was asked but couldn't find the right answer across stackOverFlow so I'm asking ..
I'm trying to implement dark mode into my app, but unfortunately it doesn't work well for me while using tableviews, it does changes my background and stuff, but I can't change the color of my groups in my tableview.
Here's an image to illustrate the problem:
https://imgur.com/a/h4A3zOZ (can't upload it here cause its too big).
Also Here is my Code:
// MARK: - Premium Section - DarkMode + Graph:
#IBAction func darkModeSwitch(_ sender: UISwitch) {
let current = sender.isOn ? Theme.dark : Theme.light
if #available(iOS 13.0, *) {
// overrideUserInterfaceStyle = UIUserInterfaceStyle(rawValue: current.stateMode)!
//STEP1: Saving User Defaults Switcher:
saveSwitchToggleDarkMode(switcherState: sender.isOn)
//STEP2: Setting UI Colors Of Settings View:
self.tableView.backgroundColor = current.backgroundColor
///Setting up the barTint Color:
self.navigationController?.navigationBar.barTintColor = current.barTintColor
///Setting up the title text color:
self.navigationController?.navigationBar.titleTextAttributes = [NSAttributedString.Key.foregroundColor:current.textColor]
///Changing back color in navigation controller:
self.navigationController?.navigationBar.backItem?.backBarButtonItem?.tintColor = current.backItemColor
}
}
You should change the mode on the window's level to apply changes to all your controls e.g:
if #available(iOS 13, *) {
UIApplication.shared.delegate?.window??.overrideUserInterfaceStyle = .dark
}
An alternative (and perhaps easier) method to implement dark mode is to use the iOS dark mode feature that you can trigger in settings.If you want to implement this you can create a custom color set by going to your Assets.xcassets and pressing the plus mark on the bottom -> new color set. On the attributes inspector, name your color under name, and under Appearances, select 'Any, Light, Dark' now you will have a place for 3 different colors. Under Light, put the light mode color, on the dark, the dark mode color.
Then on the place where you wish to implement this color,you can change the color to your custom color in the storyboard like so :-
or you can change it in code with something like
myButton.backgroundColor = UIColor(named: "TestColor")
When the user triggers the Dark mode through their control center or settings, the app will also automatically change accordingly. You can test this by going to settings -> Developer -> Dark appearance or by going to Features -> Toggle Appearance or simply press Shift + Command + A
However this method means that you will not have an independent dark mode because it will only be triggered if the device itself is dark-mode enabled.

Make every collectionview cell a different color upon click based on order clicked

I want the color of the first selected cell in my collection view to correspond with the first color in an array, next selected cell to correspond with the second color in the array, etc. Which means I have an array
let colorArray:[UIColor] = [UIColor.blue, UIColor.green, UIColor.red]
I want the first selected cell blue, next green, next red. If I unselect two of the three cells, they are no longer colored and the next selected cell would be green.
I know there are a bunch of ways to do it, but I'm setting the background color through the setting of the isHighlighted property of the cell, so I need the color to be passed to the cell before the highlighted property is set. The method didSelectItemAt is called after the property is set so it isn't working.
override var isHighlighted: Bool {
didSet {
if isHighlighted {
//set background color here
}
It would be best if there was a comparable method to willSelectAtPath like with tableview cells, but all I've found is shouldSelectItemAt and it isn't called before the highlighted property is set.
Right now I'm looping through all visible cells and setting their current color property to the current active color, but that seems cumbersome.

Xcode: Setting UIControl background colour resets on orientation change

In short, I made 'radio button' like buttons. When you press on button, its background gets set to dark green, and the others are set to light green.
However, when I rotate the device (I'm using the emulator) then the colour resets to light green - it's original colour as set up in the .xib file.
Each of the green 'buttons' is a UIControl. I set the colour using
button.backgroundColor = unselectedColour
So, like Alexander suggested, I managed to get this working using a property observer.
Like mentioned above, I have 3 buttons. I also have an enum to help remember which button has been selected (eg. none, button1, button2, button3). I keep a variable to keep track of this enum, with the property observer like so:
/** Remember the last trailer selection made. */
var lastSelection: MyEnum = .none {
willSet {
setButtonColours(selection: newValue)
}
didSet {
}
}
Each button's action sets the lastSelection variable, which then triggers this. Finally, I had to add this little bit of code to make sure that the redraw happens after the orientation change:
override func viewDidLayoutSubviews() {
setButtonColours(selection: lastSelection)
}
I don't know if this is the BEST solution (as I'm a noob), but it works! Thank you again Alexander for the help!

NSSearchField and NSSegmentedControl inside an NSToolbar

The setup:
I'm writing an document-based app targeting OS X 10.11 using storyboards. The main window has an NSToolbar with a 3-segment NSSegmentedControl. When the segmented control is clicked, it should toggle the collapsed state of an NSSplitViewItem in a horizontal or vertical NSSplitView. The behavior I'm trying to achieve is the same as in Xcode 7 where a segmented control in the toolbar shows/hides the Navigator/Debug Area/Utilities views.
Currently the segmented control sends an action to the first responder. The action method is implemented by an NSSplitViewController subclass, that then toggles it's NSSplitViewItem's collapsed state.
The problem:
The issue is that the toolbar also contains an NSSearchField. If the NSSearchField has focus, or even if the segmented control itself has focus, clicking on the NSSegmentedControl with the cursor does not result in the action method correctly making it's way up the responder chain to the NSSplitViewController subclass.
Attempted solutions:
Previously I worked around this issue using notifications instead of target/action, but it ended up being too convoluted in the end. Another idea is to send the message to the window controller, which would then pass it to it's content view controller, which would pass it to the vertical split view controller that would then send the message (if needed) to the horizontal split view controller. While I know this would work, it also seemed like an ugly solution having to add code to 2 additional files that simply passed a message along, I thought this was what using the responder chain avoided.
Any insights would be grealy appreciated.
Final solution:
I realized that wiring the segmented control's action up to the first responder only makes sense if the key view context is important. In this case the segmented control should toggle the collapsed state of split view items in multiple nested split views, regardless of what the key view is.
Define an enumeration to represent areas of the split view:
enum SplitViewArea : Int {
// The raw values must match the order of the segmented control
case left, top, right
}
Define a protocol to communicate that a split view area should be toggled:
protocol SplitViewTogglable {
func toggleSplitViewItem(matching area: SplitViewArea)
}
Implement the segmented control action method in the window controller:
#IBAction func segmentedControlSelectionStateDidChange(_ sender: Any) {
guard let segmentedControl = sender as? NSSegmentedControl else { return }
guard let area = SplitViewArea(rawValue: segmentedControl.selectedSegment) else { return }
guard let togglable = contentViewController as? SplitViewTogglable else { return }
togglable.toggleSplitViewItem(matching: area)
}
Implement the SplitViewTogglable protocol's method in the NSSplitViewController subclass:
func toggleSplitViewItem(matching area: SplitViewArea) {
switch area {
case .left:
leftSplitViewItem.isCollapsed = !leftSplitViewItem.isCollapsed
case .top:
// Nested NSSplitViewController that adopts SplitViewTogglable
if let togglable = centerSplitViewItem.viewController as? SplitViewTogglable {
togglable.toggleSplitViewItem(matching: area)
}
case .right:
rightSplitViewItem.isCollapsed = !rightSplitViewItem.isCollapsed
}
}
Is the NSSplitViewController set as the contentViewController of the window?
As part of the responder chain search for an action target, the window will consider its contentViewController as a supplemental target if it responds to the action selector.
When the search field has key focus the responder chain does not go through the normal content area, and instead goes through the toolbar to the window. So the only way the NSSplitViewController could be a part of that search is to be the contentViewController.