Multicolor Complication Text - swift

I am creating a .graphicCorner ClockKit complication using the template CLKComplicationTemplateGraphicCornerTextImage. As mentioned in the Tech Talk Developing Complications for Apple Watch Series 4 it should be possible to combine multiple differently tinted Text Providers.
Unfortunately I can make it work.
Here's my code from ComplicationController.swift
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTemplate?) -> Void) {
switch complication.family {
…
case .graphicCorner:
if #available(watchOSApplicationExtension 5.0, *) {
let template = CLKComplicationTemplateGraphicCornerStackText()
let firstTextProvider = CLKSimpleTextProvider(text: "first")
firstTextProvider.tintColor = UIColor.green
let secondTextProvider = CLKSimpleTextProvider(text: "second")
secondTextProvider.tintColor = UIColor.red
let thirdTextProvider = CLKSimpleTextProvider(text: "third")
thirdTextProvider.tintColor = UIColor.blue
template.outerTextProvider = firstTextProvider
template.innerTextProvider = CLKTextProvider.localizableTextProvider(withStringsFileFormatKey: "STRINGFORMAT", textProviders: [secondTextProvider, thirdTextProvider])
handler(template)
} else {
handler(nil) // Fallback on earlier versions
}
default:
handler(nil)
}
}
and the content of my ckcomplication.strings
"STRINGFORMAT" = "%# %#";
No text will show up. What am I doing wrong here? I appreciate any idea or working examples.

This seems to be the expected behaviour, at least for outerTextProvider:
The complication ignores the text provider’s tint color. It always displays the outer text as white.
outerTextProvider
It works for the innerTextProvider though, at least in here:
case .graphicCorner:
let graphicCorner = CLKComplicationTemplateGraphicCornerStackText()
let metarColor = UIColor.green
graphicCorner.outerTextProvider = CLKSimpleTextProvider(text: "EGLL")
graphicCorner.innerTextProvider = CLKSimpleTextProvider(text: "MVFR 27' ago")
graphicCorner.innerTextProvider.tintColor = metarColor ?? UIColor.white
let entry = CLKComplicationTimelineEntry(date: NSDate( as Date, complicationTemplate : graphicCorner)
timelineEntries.append(entry)
Screenshot

I ran into the same problem. I couldn't get CLKTextProvider.localizableTextProvider(withStringsFileFormatKey:, textProviders: ) combined with a ckcomplication.strings to work.
I ended up using this Objective-C category on CLKTextProvider.
It needed little customization for my own needs, but other than that it seems to work for me, I now get multi-coloured text in the inner ring.

Related

WatchOS complication getPlaceholderTemplate is being ignored

I have a complication working perfectly well on my Apple Watch (simulator and hardware). However, I cannot seem to get the representation to display correctly when you choose which app to assign to a complication area. In my case, graphic corner. Its showing the display name of the app with "----" under it. In my getPlaceholderTemplate protocol method, I am using CLKComplicationTemplateGraphicCornerTextImage - which is being ignored.
Here is my protocol method.
func getPlaceholderTemplate(for complication: CLKComplication,
withHandler handler: #escaping
(CLKComplicationTemplate?) -> Void)
{
let ft = CLKSimpleTextProvider(text: "Aware")
let img = UIImage(systemName: "headphones")
let tintedImageProvider = CLKImageProvider(onePieceImage: img!)
let finalImage = CLKFullColorImageProvider(fullColorImage: img!, tintedImageProvider: tintedImageProvider)
if complication.family == .graphicCorner {
let thisTemplate = CLKComplicationTemplateGraphicCornerTextImage(textProvider: ft, imageProvider: finalImage)
handler(thisTemplate)
return
} else {
print("Complication not supported.")
}
handler(nil)
}
So this seemingly isn't being used. For the Simulator I have done the "Device > Erase all content and settings" just to make sure nothing old is cached. Any idea why it's defaulting to a UI I would prefer not have? Again, this is in the complication picker only, everywhere else it's working and looking great.
Example screenshot of how it's being represented.screenshot

AdjustsFontSizeToFitWidth equivalent for iOS15 UIButton

I'm using UIKit, not SwiftUI. Previously, I used setAttributedTitle and I also set AdjustsFontSizeToFitWidth to true, and the font size shrinks to match the button width. I can't seem to match this behavior with the new button configurations and swift's AttributedString.
Here's what I have:
var config = UIButton.Configuration.filled()
config.imagePadding = 5
config.imagePlacement = .trailing
config.image = UIImage(systemName: "ellipsis.circle",
withConfiguration: UIImage.SymbolConfiguration(scale: .medium))
config.titlePadding = 10
config.cornerStyle = .capsule
let handler: UIButton.ConfigurationUpdateHandler = { button in
button.configuration?.background.backgroundColor = Colors.specLabel
button.configuration?.baseForegroundColor = Colors.greyText
}
myButton.configuration = config
myButton.configurationUpdateHandler = handler
And then
guard let attributedString = try? AttributedString(markdown: "hello **hello** hello") else { return }
myButton.configuration?.attributedTitle = attributedString
Every time, I get a multi-line button. Setting numberOfLines, lineBreakMode and adjustsFontSizeToFitWidth is all ignored. Any ideas?

Accessing NSWindow-like properties in Catalyst macOS app

I am thinking about porting my macOS app to Catalyst.
My app shows a transparent window (no title bar, clear background) on top of all other apps windows (dock included).
To do that, in the non-catalyst code I use:
window.isOpaque = false
window.hasShadow = false
window.backgroundColor = .clear
window.styleMask = .borderless
window.isMovableByWindowBackground = true
window.level = .statusBar
Using UIKit, I was only able to remove the toolbar so far:
window.titleBar.titleVisibility
...But no clue about the other settings.
I plan to make the app available on the App Store in the future, but if the only way to do so is and hack with a private API, that's fine.
Any ideas?
Thanks in advance
There is no official API for doing that, but you can easily access the NSWindow instance and modify it directly. You can do that manually or using some library like Dynamic (Full disclosure: I'm the author):
let window = Dynamic.NSApplication.sharedApplication.delegate.hostWindowForUIWindow(uiWindow)
window.isOpaque = false
window.hasShadow = false
window.backgroundColor = Dynamic.NSColor.clearColor
window.styleMask = 0 /*borderless*/
window.isMovableByWindowBackground = true
window.level = 25 /*statusBar*/
I have some success removing the close button on Catalyst by calling a function from the viewDidAppear(). I called it AppDelegate().disableTitleBarButtons(). Has to be from view did appear.
AppDelegate().disableTitleBarButtons() is as follows
func disableTitleBarButtons() {
func bitSet(_ bits: [Int]) -> UInt {
return bits.reduce(0) { $0 | (1 << $1) }
}
func property(_ property: String, object: NSObject, set: [Int], clear: [Int]) {
if let value = object.value(forKey: property) as? UInt {
object.setValue((value & ~bitSet(clear)) | bitSet(set), forKey: property)
}
}
// disable full-screen button
if let NSApplication = NSClassFromString("NSApplication") as? NSObject.Type,
let sharedApplication = NSApplication.value(forKeyPath: "sharedApplication") as? NSObject,
let windows = sharedApplication.value(forKeyPath: "windows") as? [NSObject]
{
for window in windows {
let resizable = 4
property("styleMask", object: window, set: [], clear: [resizable])
let fullScreenPrimary = 7
let fullScreenAuxiliary = 8
let fullScreenNone = 9
property("collectionBehavior", object: window, set: [fullScreenNone], clear: [fullScreenPrimary, fullScreenAuxiliary])
}
}
}
Where is says let resizable = 4,
Change to 3 for no Maximise,
Change to 2 for No minimise,
Change to 1 of No Close button.
Play with the other numbers or stylemask settings also. Good luck

Style placeholder text NSSearchField and NSTextField?

I've been creating a MacOS app and am having trouble styling the font of an NSSearchField (named searchField). My code so far is as follows:
Declared at top of single main viewController class:
let normalTextStyle = NSFont(name: "PT Mono", size: 14.0)
let backgroundColour = NSColor(calibratedHue: 0.6,
saturation: 0.5,
brightness: 0.2,
alpha: 1.0)
let normalTextColour = NSColor(calibratedHue: 0.5,
saturation: 0.1,
brightness: 0.9,
alpha: 1.0)
Declared in viewDidLoad:
searchField.backgroundColor = backgroundColour
searchField.textColor = normalTextColour
searchField.font = normalTextStyle
searchField.centersPlaceholder = false
searchField.currentEditor()?.font = normalTextStyle
let attrStr = NSMutableAttributedString(string: "Search...",
attributes: [NSForegroundColorAttributeName: normalTextColour])
searchField.placeholderAttributedString = attrStr
Generally this works except in one condition: when the search field has focus but no search term has been entered. In this case the placeholder text has the correct colour but the font seems to return to the default (Helvetica 12 point?). As soon as something is typed in or the field loses focus, then the correct font is used once more.
I have tried with no luck looking through the Apple docs for some kind of font or colour settings not currently being set. I have fiddled about with all the font setting I could find in the interface builder, including cocoa bindings and the normal settings in the inspector.
Do I need to set some value of the currentEditor? I am guessing not because the font is changed once text is entered.. I am stuck - can anyone help?
EDIT: I've now tried with an NSTextField and the results are the same. Does anyone have any ideas?
I eventually managed to find an answer. I created a new class TitleTextFormatter of type Formatter, which is 'is intended for subclassing. A custom formatter can restrict the input and enhance the display of data in novel ways'. All I needed to do was override certain default functions to get what I needed:
import Cocoa
class TitleTextFormatter: Formatter {
override func string(for obj: Any?) -> String? {
/*
* this function receives the object it is attached to.
* in my case it only ever receives an NSConcreteAttributedString
* and returns a plain string to be formatted by other functions
*/
var result: String? = nil
if let attrStr = obj as? NSAttributedString {
result = attrStr.string
}
return result
}
override func getObjectValue( _ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?,
for string: String,
errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
/*
* this function in general is overridden to provide an object created
* from the input string. in this instance, all I want is an attributed string
*/
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
NSAttributedStringKey.font: NSFont.titleText,
NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
let titleAttrStr = NSMutableAttributedString(string: string,
attributes: titleAttributes)
obj?.pointee = titleAttrStr
return true
}
override func attributedString(for obj: Any,
withDefaultAttributes attrs: [NSAttributedStringKey : Any]? = nil) -> NSAttributedString? {
/*
* is overridden to show that an attributed string is created from the
* formatted object. this happens to duplicate what the previous function
* does, only because the object I want to create from string is an
* attributed string
*/
var titleAttrStr: NSMutableAttributedString?
if let str = string(for: obj) {
let titleParagraphStyle = NSMutableParagraphStyle()
titleParagraphStyle.alignment = .center
let titleAttributes = [NSAttributedStringKey.foregroundColor: NSColor.mainText,
NSAttributedStringKey.font: NSFont.titleText,
NSAttributedStringKey.paragraphStyle: titleParagraphStyle]
titleAttrStr = NSMutableAttributedString(string: str,
attributes: titleAttributes)
}
return titleAttrStr
}
}
and then in viewDidLoad I added the following:
let titleTextFormatter = TitleTextFormatter()
titleTextField.formatter = titleTextFormatter

WatchKit 2 Complication Text Only Shows Up in Preview

I'm trying to develop a very simple complication for watchkit2 that says "Hi" from a simple text provider.
I've managed to achieve some strange behavior; I can see the text when the complication is clicked or when you are previewing it from the customize watchface screen, but not when the watchface is displayed. Have a look:
Any ideas what might be causing this?
My text provider looks like this
var textProvider: CLKSimpleTextProvider
override init() {
textProvider = CLKSimpleTextProvider()
textProvider.text = "Hi"
textProvider.shortText = "HI"
textProvider.tintColor = UIColor.whiteColor()
super.init()
}
And my get getPlaceholderTemplateForComplication looks like
func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
switch complication.family {
case .ModularSmall:
let stemplate = CLKComplicationTemplateModularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
case .CircularSmall:
let stemplate = CLKComplicationTemplateCircularSmallSimpleText()
stemplate.tintColor = UIColor.whiteColor()
stemplate.textProvider = textProvider
handler(stemplate)
default:
handler(nil)
}
}
While customizing watch face, Apple Watches calls getPlaceholderTemplateForComplication:withHandler: to show placeholder text. Since you've implemented it - you can see "Hi". That is cool.
But when watch face displayed, it calls another methods, such as:
getCurrentTimelineEntryForComplication:withHandler:
getTimelineEntriesForComplication:beforeDate:limit:withHandler:
getTimelineEntriesForComplication:afterDate:limit:withHandler:
And it seems like you're not implemented them. So implementing these method will resolve your issue.
You can find more detailed information about these methods in this WWDC 2015 Tutorial: https://developer.apple.com/videos/wwdc/2015/?id=209