I am trying to display an OS X application statusItem in the System Status Bar and am having success with everything except the fact that the title is being cut off. I am initializing everything like so:
let statusItem = NSStatusBar.systemStatusBar().statusItemWithLength(-1)
func applicationDidFinishLaunching(aNotification: NSNotification) {
let icon = NSImage(named: "statusIcon")
icon?.template = true
statusItem.image = icon
statusItem.menu = statusMenu
statusItem.title = "This is a test title"
}
The problem is the statusItem.title is appearing like so:
As you can see the application next to mine (iStatMenuBar) is cutting off the title to my application (or something similar is happening)
If I comment out the icon for the statusItem, it works and shows the entire title but when I re-add the icon it cuts off again. Is there a way for the two (icon and title) to co exist? I have reviewed some Apple docs and may have missed a critical piece which explains this.
Thanks guys.
One option would be to assign a custom view to your statusBarItem and within that view's class override drawRect(dirtyRect: NSRect) e.g.
private var icon:StatusMenuView?
let bar = NSStatusBar.systemStatusBar()
item = bar.statusItemWithLength(-1)
self.icon = StatusMenuView()
item!.view = icon
and StatusMenuView might look like:
// This is an edited copy & paste from one of my personal projects so it might be missing some code
class StatusMenuView:NSView {
private(set) var image: NSImage
private let titleString:NSString = "really long title..."
init() {
icon = NSImage(named: "someImage")!
let myWideStatusBarItemFrame = CGRectMake(0, 0, 180.0, NSStatusBar.systemStatusBar().thickness)
super.init(frame.rect)
}
override func drawRect(dirtyRect: NSRect)
{
self.item.drawStatusBarBackgroundInRect(dirtyRect, withHighlight: self.isSelected)
let size = self.image.size
let rect = CGRectMake(2, 2, size.width, size.height)
self.image.drawInRect(rect)
let titleRect = CGRectMake( 2 + size.width, dirtyRect.origin.y, 180.0 - size.width, size.height)
self.titleString.drawInRect(titleRect, withAttributes: nil)
}
}
Now, the above might change your event handling, you'll need to handle mouseDown in the StatusMenuView class.
Related
I developed this app using Xcode 10.1 (Swift 4.2 I believe) and toolbar looks good:
But I ported in over to my Xcode 11.6 (Swift 5.2 I believe), now the right-most item on the toolbar is no longer on the far right:
Before the port, I added that plus button - the checkbox (Show Hidden) was one of the first items and was never changed since.
Also the buttons function as expected - their actions fire OK.
Note: This app uses no Storyboards or IB things.
Since my code in split over several files (with lots of unrelated code), I will give an overview.
class ToolbarController: NSObject, NSToolbarDelegate {
let toolbar = NSToolbar(identifier: NSToolbar.Identifier("toolbar"))
lazy var toolbarItem1: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "Btn1"))
let button = NSButton(frame: NSRect(x: 0, y: 0, width: 40, height: 1))
button.target = self
...
toolbarItem.view = button
return toolbarItem
}() // defined as "lazy" to allow "self"
var toolbarItemSpace: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.space)
return toolbarItem
}()
...
var toolbarItemFlexSpace: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
return toolbarItem
}()
lazy var toolbarItemShowHidden: NSToolbarItem = {
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier(rawValue: "Checkbox"))
let box = NSBox(frame: NSRect(x: 0, y: 0, width: 120, height: 40))
let button = NSButton() // put in NSBox to use isolated action
...
button.target = self
box.contentView = button
box.sizeToFit() // tightly fit the contents (button)
toolbarItem.view = box
return toolbarItem
}()
lazy var tbItems: [NSToolbarItem] = [toolbarItem1, ...]
That class defines the toolbar, add the items, implements all those toolbar (delegate) methods and makes it the delegate. My NSWindowController then set it to the application toolbar.
Can anyone see an issue with my code that causes the above?
Otherwise, is this a bug?
Had the same problem with Swift 5.0 ... even though the documentation says minSize and maxSize are deprecated, I solved this like:
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
toolbarItem.minSize = NSSize(width: 1, height: 1)
toolbarItem.maxSize = NSSize(width: 1000, height: 1) //just some large value
return toolbarItem
Maybe this works as well for Swift 5.2
Found a way without living w/ the warning & not using the deprecated properties.
We create a transparent subview for the NASToolbarItem with the min / max constraints in advance, so the system has a way to calc the min and max size itself.
let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.flexibleSpace)
// view to be hosted in the flexible space toolbar item
let view = NSView(frame: CGRect(origin: .zero, size: CGSize(width: MIN_TOOLBAR_ITEM_W, height: MIN_TOOLBAR_H)))
view.widthAnchor.constraint(lessThanOrEqualToConstant: MAX_TOOLBAR_ITEM_W).isActive = true
view.widthAnchor.constraint(greaterThanOrEqualToConstant: MIN_TOOLBAR_ITEM_W).isActive = true
// set the view and return
toolbarItem?.view = view
return toolbarItem
I am making an app where a user can click anywhere on the window and a NSTextView is added programmatically at the mouse location. I have got it working with the below code but I want this NSTextView to horizontally expand until it reaches the edge of the screen and then grow vertically. It currently has a fixed width and when I add more characters, the text view grows vertically (as expected) but I also want it to grow horizontally. How can I achieve this?
I have tried setting isHorizontallyResizable and isVerticallyResizable to true but this doesn't work. After researching for a while, I came across this https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/TextStorageLayer/Tasks/TrackingSize.html but this didn't work for me either.
Code in my ViewController to add the NSTextView to its view:
private func addText(at point: NSPoint) {
let textView = MyTextView(frame: NSRect(origin: point, size: CGSize(width: 150.0, height: 40.0)))
view.addSubview(textView)
}
And, MyTextView class looks like below:
class MyTextView: NSTextView {
override func viewWillDraw() {
isHorizontallyResizable = true
isVerticallyResizable = true
isRichText = false
}
}
I have also seen this answer https://stackoverflow.com/a/54228147/1385441 but I am not fully sure how to implement it. I have added this code snippet in MyTextView and used it like:
override func didChangeText() {
frame.size = contentSize
}
However, I think I am using it incorrectly. Ergo, any help would be much appreciated.
I'm a bit puzzled, because you're adding NSTextView to a NSView which is part of the NSViewController and then you're talking about the screen width. Is this part of your Presentify - Screen Annotation application? If yes, you have a full screen overlay window and you can get the size from it (or from the view controller's view).
view.bounds.size // view controller's view size
view.window?.frame.size // window size
If not and you really need to know the screen size, check the NSWindow & NSScreen.
view.window?.screen?.frame.size // screen size
Growing NSTextView
There's no any window/view controller's view resizing behavior specified.
import Cocoa
class BorderedTextView: NSTextView {
override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let path = NSBezierPath(rect: bounds)
NSColor.red.setStroke()
path.stroke()
}
}
class ViewController: NSViewController {
override func mouseUp(with event: NSEvent) {
// Convert point to the view coordinates
let point = view.convert(event.locationInWindow, from: nil)
// Initial size
let size = CGSize(width: 100, height: 25)
// Maximum text view width
let maxWidth = view.bounds.size.width - point.x // <----
let textView = BorderedTextView(frame: NSRect(origin: point, size: size))
textView.insertionPointColor = .orange
textView.drawsBackground = false
textView.textColor = .white
textView.isRichText = false
textView.allowsUndo = false
textView.font = NSFont.systemFont(ofSize: 20.0)
textView.isVerticallyResizable = true
textView.isHorizontallyResizable = true
textView.textContainer?.widthTracksTextView = false
textView.textContainer?.heightTracksTextView = false
textView.textContainer?.size.width = maxWidth // <----
textView.maxSize = NSSize(width: maxWidth, height: 10000) // <----
view.addSubview(textView)
view.window?.makeFirstResponder(textView)
}
}
I finally got it to work (except for one minor thing). The link from Apple was the key here but they haven't described the code completely, unfortunately.
The below code work for me:
class MyTextView: NSTextView {
override func viewWillDraw() {
// for making the text view expand horizontally
textContainer?.heightTracksTextView = false
textContainer?.widthTracksTextView = false
textContainer?.size.width = 10000.0
maxSize = NSSize(width: 10000.0, height: 10000.0)
isHorizontallyResizable = true
isVerticallyResizable = true
isRichText = false
}
}
That one minor thing which I haven't been able to figure out yet is to limit expanding horizontally until the edge of the screen is reached. Right now it keeps on expanding even beyond the screen width and, in turn, the text is hidden after the screen width.
I think if I can somehow get the screen window width then I can replace 10000.0 with the screen width (minus the distance of text view from left edge) and I can limit the horizontal expansion until the edge of the screen. Having said that, keeping it 10000.0 won't impact performance as described in the Apple docs.
FirebaseUI has a nice pre-buit UI for Swift. I'm trying to position an image view above the login buttons on the bottom. In the example below, the imageView is the "Hackathon" logo. Any logo should be able to show in this, if it's called "logo", since this shows the image as aspectFit.
According to the Firebase docs page:
https://firebase.google.com/docs/auth/ios/firebaseui
You can customize the signin screen with this function:
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
return FUICustomAuthPickerViewController(nibName: "FUICustomAuthPickerViewController",
bundle: Bundle.main,
authUI: authUI)
}
Using this code & poking around with subviews in the debuggers, I've been able to identify and color code views in the image below. Unfortunately, I don't think that the "true" size of these subview frames is set until the view controller presents, so trying to access the frame size inside these functions won't give me dimensions that I can use for creating a new imageView to hold a log. Plus accessing the views with hard-coded index values like I've done below, seems like a pretty bad idea, esp. given that Google has already changed the Pre-Built UI once, adding a scroll view & breaking the code of anyone who set the pre-built UI's background color.
func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
// Create an instance of the FirebaseAuth login view controller
let loginViewController = FUIAuthPickerViewController(authUI: authUI)
// Set background color to white
loginViewController.view.backgroundColor = UIColor.white
loginViewController.view.subviews[0].backgroundColor = UIColor.blue
loginViewController.view.subviews[0].subviews[0].backgroundColor = UIColor.red
loginViewController.view.subviews[0].subviews[0].tag = 999
return loginViewController
}
I did get this to work by adding a tag (999), then in the completion handler when presenting the loginViewController I hunt down tag 999 and call a function to add an imageView with a logo:
present(loginViewController, animated: true) {
if let foundView = loginViewController.view.viewWithTag(999) {
let height = foundView.frame.height
print("FOUND HEIGHT: \(height)")
self.addLogo(loginViewController: loginViewController, height: height)
}
}
func addLogo(loginViewController: UINavigationController, height: CGFloat) {
let logoFrame = CGRect(x: 0 + logoInsets, y: self.view.safeAreaInsets.top + logoInsets, width: loginViewController.view.frame.width - (logoInsets * 2), height: self.view.frame.height - height - (logoInsets * 2))
// Create the UIImageView using the frame created above & add the "logo" image
let logoImageView = UIImageView(frame: logoFrame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
// loginViewController.view.addSubview(logoImageView) // Add ImageView to the login controller's main view
loginViewController.view.addSubview(logoImageView)
}
But again, this doesn't seem safe. Is there a "safe" way to deconstruct this UI to identify the size of this button box at the bottom of the view controller (this size will vary if there are multiple login methods supported, such as Facebook, Apple, E-mail)? If I can do that in a way that avoids the hard-coding approach, above, then I think I can reliably use the dimensions of this button box to determine how much space is left in the rest of the view controller when adding an appropriately sized ImageView. Thanks!
John
This should address the issue - allowing a logo to be reliably placed above the prebuilt UI login buttons buttons + avoiding hard-coding the index values or subview locations. It should also allow for properly setting background color (also complicated when Firebase added the scroll view + login button subview).
To use: Create a subclass of FUIAuthDelegate to hold a custom view controller for the prebuilt Firebase UI.
The code will show the logo at full screen behind the buttons if there isn't a scroll view or if the class's private constant fullScreenLogo is set to false.
If both of these conditions aren't meant, the logo will show inset taking into account the class's private logoInsets constant and the safeAreaInsets. The scrollView views are set to clear so that a background image can be set, as well via the private let backgroundColor.
Call it in any signIn function you might have, after setting authUI.providers. Call would be something like this:
let loginViewController = CustomLoginScreen(authUI: authUI!)
let loginNavigationController = UINavigationController(rootViewController: loginViewController)
loginNavigationController.modalPresentationStyle = .fullScreen
present(loginNavigationController, animated: true, completion: nil)
And here's one version of the subclass:
class CustomLoginScreen: FUIAuthPickerViewController {
private var fullScreenLogo = false // false if you want logo just above login buttons
private var viewContainsButton = false
private var buttonViewHeight: CGFloat = 0.0
private let logoInsets: CGFloat = 16
private let backgroundColor = UIColor.white
private var scrollView: UIScrollView?
private var viewContainingButton: UIView?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// set color of scrollView and Button view inside scrollView to clear in viewWillAppear to avoid a "color flash" when the pre-built login UI first appears
self.view.backgroundColor = UIColor.white
guard let foundScrollView = returnScrollView() else {
print("😡 Couldn't get a scrollView.")
return
}
scrollView = foundScrollView
scrollView!.backgroundColor = UIColor.clear
guard let foundViewContainingButton = returnButtonView() else {
print("😡 No views in the scrollView contain buttons.")
return
}
viewContainingButton = foundViewContainingButton
viewContainingButton!.backgroundColor = UIColor.clear
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Create the UIImageView at full screen, considering logoInsets + safeAreaInsets
let x = logoInsets
let y = view.safeAreaInsets.top + logoInsets
let width = view.frame.width - (logoInsets * 2)
let height = view.frame.height - (view.safeAreaInsets.top + view.safeAreaInsets.bottom + (logoInsets * 2))
var frame = CGRect(x: x, y: y, width: width, height: height)
let logoImageView = UIImageView(frame: frame)
logoImageView.image = UIImage(named: "logo")
logoImageView.contentMode = .scaleAspectFit // Set imageView to Aspect Fit
logoImageView.alpha = 0.0
// Only proceed with customizing the pre-built UI if you found a scrollView or you don't want a full-screen logo.
guard scrollView != nil && !fullScreenLogo else {
print("No scrollView found.")
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
self.view.addSubview(logoImageView)
self.view.sendSubviewToBack(logoImageView) // otherwise logo is on top of buttons
return
}
// update the logoImageView's frame height to subtract the height of the subview containing buttons. This way the buttons won't be on top of the logoImageView
frame = CGRect(x: x, y: y, width: width, height: height - (viewContainingButton?.frame.height ?? 0.0))
logoImageView.frame = frame
self.view.addSubview(logoImageView)
UIView.animate(withDuration: 0.25, animations: {logoImageView.alpha = 1.0})
}
private func returnScrollView() -> UIScrollView? {
var scrollViewToReturn: UIScrollView?
if self.view.subviews.count > 0 {
for subview in self.view.subviews {
if subview is UIScrollView {
scrollViewToReturn = subview as? UIScrollView
}
}
}
return scrollViewToReturn
}
private func returnButtonView() -> UIView? {
var viewContainingButton: UIView?
for view in scrollView!.subviews {
viewHasButton(view)
if viewContainsButton {
viewContainingButton = view
break
}
}
return viewContainingButton
}
private func viewHasButton(_ view: UIView) {
if view is UIButton {
viewContainsButton = true
} else if view.subviews.count > 0 {
view.subviews.forEach({viewHasButton($0)})
}
}
}
Hope this helps any who have been frustrated trying to configure the Firebase pre-built UI in Swift.
This issue came up in relation to a problem I had yesterday for which I should be able to create a workaround. As I investigated further, I found that it occurs more broadly than I originally thought. I had previously only noticed it in displayed text that included at least one newline character, but that's not the case below.
The problem seems to result from using the NSLayoutManager's boundingRect method to obtain (among other things) individual character widths and then using those widths to set characters' UITextView frame width properties. Doing so apparently causes the setting of the text view's backgroundColor to UIColor.clear to be ignored (i.e., the background becomes opaque). The Playground code below reproduces the problem, shown in red text, and shows the workaround of using a constant for widths, in black. The tighter the kerning, the more pronounced the effect.
Is this a bug? Or is it a quirk due to something else?
//: A UIKit based Playground for presenting user interface
import UIKit
import PlaygroundSupport
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.bounds = CGRect(x: -100, y: -100, width: 200, height: 200)
view.backgroundColor = .white
let str = "..T.V.W.Y.."
let strStorage = NSTextStorage(string: str)
let layoutManager = NSLayoutManager()
strStorage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: view.bounds.size)
textContainer.lineFragmentPadding = 0.0
layoutManager.addTextContainer(textContainer)
let strArray = Array(str)
struct CharInfo {
var char: Character
var origin: CGPoint?
var size: CGSize?
}
var charInfoArray = [CharInfo]()
for index in 0..<str.count {
charInfoArray.append(CharInfo.init(char: strArray[index], origin: nil, size: nil))
let charRange = NSMakeRange(index, 1)
let charRect = layoutManager.boundingRect(forGlyphRange: charRange, in: textContainer)
charInfoArray[index].origin = charRect.origin
charInfoArray[index].size = charRect.size
}
for charInfo in charInfoArray {
let textView0 = UITextView()
textView0.backgroundColor = UIColor.clear // Ignored in this case!!
textView0.text = String(charInfo.char)
textView0.textContainerInset = UIEdgeInsets.zero
let size0 = charInfo.size!
textView0.frame = CGRect(origin: charInfo.origin!, size: size0)
textView0.textContainer.lineFragmentPadding = CGFloat(0.0)
textView0.textColor = UIColor.red
view.addSubview(textView0)
let textView1 = UITextView()
textView1.backgroundColor = UIColor.clear // Required
textView1.text = String(charInfo.char)
textView1.textContainerInset = UIEdgeInsets.zero
var size1 = charInfo.size!
size1.width = 20 // But changing .height has no effect on opacity
textView1.frame = CGRect(origin: charInfo.origin!, size: size1)
textView1.frame = textView1.frame.offsetBy(dx: 0, dy: 20)
textView1.textContainer.lineFragmentPadding = CGFloat(0.0)
textView1.textColor = UIColor.black
view.addSubview(textView1)
}
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
This does seem to be a bug, but it's with NSLayoutManager's instance method boundingRect(forGlyphRange:in:). It only looks like it could be a transparency change.
According to Apple's documentation, boundingRect(forGlyphRange:in:) is supposed to "[return] a single bounding rectangle (in container coordinates) enclosing all glyphs and other marks drawn in the given text container for the given glyph range, including glyphs that draw outside their line fragment rectangles and text attributes such as underlining." But that's not what it's doing.
In this case, the width of each boundingRect gets reduced by the amount that the next glyph was shifted to the left, due to kerning. You can test this, for example, using str = "ToT" and adding print(size0.width) right after it is set. You'll get this:
6.0 // "T"; should have been 7.330078125
6.673828125 // "o"
7.330078125 // "T"
Until this bug is fixed, a workaround would be to calculate glyph size for each character in isolation.
So the scenario is that there is a view where the user can enable/disable subtitles in an app I'm helping to develop.
On that view there is a sample text saying "This is what captions look like", and at the moment it's just a basic, unstyled UILabel. Ideally I would like it to be styled in a similar manner to how the user has customized their captions in the System Settings.
Is this possible in any way? I've envisioned two possible method:
Create an AVPlayer instance and a .vtt file with the text, load it into the view and pause the player. I'm not sure this is possible with a sample video (and it would somehow have to be transparent as there is an image behind the sample sub text).
Somehow get all the styling (font, size, background color, etc) the user has set for their subtitle and create an attributed string to match that
Method 2 seems like the most feasible way, but I don't know if we have access to those settings in code.
So I figured it out! It basically makes use a combination of the Media Accessibility API, which allows you to get the values the user has chosen for their captions/subtitle settings, Attributed Strings, and a subclass UILabel (although this could maybe be substituted with a UITextView as that will allow you to set it's UIEdgeInsets natively)
So, first, the subclass is to allow the UILabel to be inset. This is because captions can have a background color AND a text highlight color and without the inset, the text highlight is all you see. So the function the subclass is simple:
class InsetUILabel: UILabel {
override func drawTextInRect(rect: CGRect) {
let inset: CGFloat = 15
let insets: UIEdgeInsets = UIEdgeInsets(top: inset, left: inset/2, bottom: inset, right: inset/2)
super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}
}
And for generating the actual label. This uses a label called textSample, but you can obviously make it a little more general.
import MediaAccessibility
func styleLabel(sampleText: String) {
let domain = MACaptionAppearanceDomain.User
// Background styling
let backgroundColor = UIColor(CGColor: MACaptionAppearanceCopyWindowColor(domain, nil).takeRetainedValue())
let backgroundOpacity = MACaptionAppearanceGetWindowOpacity(domain, nil)
textSample.layer.backgroundColor = backgroundColor.colorWithAlphaComponent(backgroundOpacity).CGColor
textSample.layer.cornerRadius = MACaptionAppearanceGetWindowRoundedCornerRadius(domain, nil)
// Text styling
var textAttributes = [String:AnyObject]()
let fontDescriptor = MACaptionAppearanceCopyFontDescriptorForStyle(domain, nil, MACaptionAppearanceFontStyle.Default).takeRetainedValue()
let fontName = CTFontDescriptorCopyAttribute(fontDescriptor, "NSFontNameAttribute") as! String
let fontColor = UIColor(CGColor: MACaptionAppearanceCopyForegroundColor(domain, nil).takeRetainedValue())
let fontOpacity = MACaptionAppearanceGetForegroundOpacity(domain, nil)
let textEdgeStyle = MACaptionAppearanceGetTextEdgeStyle(domain, nil)
let textHighlightColor = UIColor(CGColor: MACaptionAppearanceCopyBackgroundColor(domain, nil).takeRetainedValue())
let textHighlightOpacity = MACaptionAppearanceGetBackgroundOpacity(domain, nil)
let textEdgeShadow = NSShadow()
textEdgeShadow.shadowColor = UIColor.blackColor()
let shortShadowOffset: CGFloat = 1.5
let shadowOffset: CGFloat = 3.5
switch(textEdgeStyle) {
case .None:
textEdgeShadow.shadowColor = UIColor.clearColor()
case .DropShadow:
textEdgeShadow.shadowOffset = CGSize(width: -shortShadowOffset, height: shortShadowOffset)
textEdgeShadow.shadowBlurRadius = 6
case .Raised:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Depressed:
textEdgeShadow.shadowOffset = CGSize(width: 0, height: -shadowOffset)
textEdgeShadow.shadowBlurRadius = 5
case .Uniform:
textEdgeShadow.shadowColor = UIColor.clearColor()
textAttributes[NSStrokeColorAttributeName] = UIColor.blackColor()
textAttributes[NSStrokeWidthAttributeName] = -2.0
default:
break
}
textAttributes[NSFontAttributeName] = UIFont(name: fontName, size: (textSample.font?.pointSize)!)
textAttributes[NSForegroundColorAttributeName] = fontColor.colorWithAlphaComponent(fontOpacity)
textAttributes[NSShadowAttributeName] = textEdgeShadow
textAttributes[NSBackgroundColorAttributeName] = textHighlightColor.colorWithAlphaComponent(textHighlightOpacity)
textSample.attributedText = NSAttributedString(string: sampleText, attributes: textAttributes)
}
Now the text highlight section makes use of shadows, with values I think look pretty good, but you might want to tweak them a tiny bit. Hope this helps!