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
Related
I tried to do like this, but it does not work, the text is not copied
if let urlScheme = URL(string: "instagram-stories://share") {
if UIApplication.shared.canOpenURL(urlScheme) {
let imageData: Data = UIImage(systemName:"pencil.circle.fill")!.pngData()!
let items:[String: Any] = ["public.utf8-plain-text": "text","com.instagram.sharedSticker.backgroundImage": imageData]
UIPasteboard.general.setItems([items])
UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
}}
I would really appreciate any advice
2 things I can think of:
First, I am not sure the below data in your array can be properly handled by pastebin
let items:[String: Any] = ["public.utf8-plain-text": "text","com.instagram.sharedSticker.backgroundImage": imageData]
Next it seems that the activity of sharing causes data in the PasteBoard to be lost so I can offer the solution to put valid data into the PasteBoard (I am using string for example, you can use something else" from the completion handler of your sharing action, something like this might solve it:
UIApplication.shared.open(urlScheme, options: [:]) { (_) in
UIPasteboard.general.string =
"click on the screen until the paste button appears: https://google.com"
}
EDIT
It seems your set up was right and on reading the docs, IG stories should handle the Paste automatically as it seems to check the pasteboard when you execute this url scheme: instagram-stories://share - so it seems IG checks the pasteboard and performs a paste programmatically and that is why the pasteboard gets cleared.
Maybe because the image you choose is black on the black instagram background, it seems nothing is shared but with some proper image the result seems fine.
The other thing I noticed after reading their docs, they do not allow you to set captions anymore, I cannot find this key anymore public.utf8-plain-text
Another idea I can offer to share text is to convert text into an image and add it as a sticker as the sticker layer comes on top of the background image layer.
You can find multiple ways to convert text to an image and it is not relevant to your solution, here is one way I used
So bringing the code together, I have this:
// Just an example to convert text to UIImage
// from https://stackoverflow.com/a/54991797/1619193
extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: size).image { _ in
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
}
}
}
// Then inside some function of yours
func someFunction() {
if let urlScheme = URL(string: "instagram-stories://share") {
if UIApplication.shared.canOpenURL(urlScheme) {
let imageData: Data = UIImage(named: "bg")!.pngData()!
let textImage: Data = "Shawn Test".image(withAttributes: [.foregroundColor: UIColor.red,
.font: UIFont.systemFont(ofSize: 30.0)],
size: CGSize(width: 300.0, height: 80.0))!.pngData()!
let items = ["com.instagram.sharedSticker.stickerImage": textImage,
"com.instagram.sharedSticker.backgroundImage": imageData]
UIPasteboard.general.setItems([items])
UIApplication.shared.open(urlScheme, options: [:], completionHandler: nil)
}
}
}
I then see this in IG stories with correct background and text as sticker which can be moved.
Only downside of using the sticker is you cannot edit the text in Instagram.
Regarding the research looks like the only one workaround to have a text/link copied in the Pasteboard when IG Story is opened is to use:
UIPasteboard.general.string = "your link here"
but you need to do it with a delay - like:
UIApplication.shared.open(instagramStoryURL, options: [:]) { success in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
UIPasteboard.general.string = "your link here"
}
}
to try to be sure the it won't override:
UIPasteboard.general.items
that contains, for example, "com.instagram.sharedSticker.stickerImage"
Also, please be careful with a delay - as iOS has some privacy restrictions to allow copy data to UIPasteboard when the App is in background (based on the tests we have less than 1 second to do that.
It means that you could try to copy the link this way:
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.willResignActiveNotification, object: nil)
}
#objc func appMovedToBackground() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.75) {
UIPasteboard.general.string = "your link here"
}
}
Anyway, there is one obvious inconvenience.
Each time you try to call "instagram-stories://share" API the first time - you face a system popup that asks for the permisson to allow to open Instagram and also to allow paste the data.
In this case we'll lose, for example, "com.instagram.sharedSticker.stickerImage" data as by the delay it will be overrided by UIPasteboard.general.string.
But we could to make it expected for users by any UI/UX solution with instructions/guide.
I have a bit of code in my app that generates a QR Code and scales it up (code reference I used from this link from Hackng with Swift. Now, I'm using the share sheet to allow the user to save the qr code to their camera roll and, it is working, but saving the image low res, and it saves to the camera roll blurry (and i assume if it is shared via other methods it will also be blurry)
Here is the code of my share sheet function:
struct ActivityView: UIViewControllerRepresentable {
let activityItems: [Any]
let applicationActivities: [UIActivity]?
func makeUIViewController(context: UIViewControllerRepresentableContext<ActivityView>) -> UIActivityViewController {
return UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
}
func updateUIViewController(_ uiViewController: UIActivityViewController, context: UIViewControllerRepresentableContext<ActivityView>) {
}
}
and here's the code in my view struct:
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [self.qrCodeImage])
}
Is there a trick to remove the interpolation on the image when it saves to the share sheet like the .interpolation(.none) on the image view itself?
Your problem is that the QR code image is actually tiny! Like really tiny:
Printing description of image:
<UIImage:0x60000202cc60 anonymous {23, 23}>
When you share this image, the way it will be displayed is dependant on the program or app that will display it, and is out of control of your app as far as I know.
However,
there is a way that you could potentially make it "pretty" in other apps, and this would be to increase the resolution to a larger amount so that when it's rendered it'll appear to have "sharp" pixels.
How would this be accomplished? I think I have an example buried somewhere in old code, I'll dig into it and see if I can find you an example ;)
Edit
I found the code:
extension UIImage {
func resized(toWidth width: CGFloat) -> UIImage? {
let canvasSize = CGSize(width: round(width), height: CGFloat(ceil(width/size.width * size.height)))
UIGraphicsBeginImageContextWithOptions(canvasSize, false, scale)
defer { UIGraphicsEndImageContext() }
let context = UIGraphicsGetCurrentContext();
context?.interpolationQuality = .none
// Set the quality level to use when rescaling
draw(in: CGRect(origin: .zero, size: canvasSize))
let r = UIGraphicsGetImageFromCurrentImageContext()
return r
}
}
The trick is to provide a way to scale the image, but the real magic is on line 7:
context?.interpolationQuality = .none
If you exclude this line, you'll get blurry images, which is what the OS does by default because you don't generally want to see the pixel edges in images.
You could use this extension like so:
.sheet(isPresented: $showShareSheet) {
ShareSheet(activityItems: [self.qrCodeImage.resized(toWidth: 512) ?? UIImage()])
}
However, this may be resizing the image way more often than necessary. Optimally you'd resize it in the same function that you generate it.
I got a project that involves a few USDZ files for the augmented reality features embedded in the app. While this works great, and we're really happy with how it performs, the built-in share button of the QLPreviewController is something that we'd like to remove. Subclassing the object doesn't have any effect, and trying to hide the rightBarButtonItem with the controller returned in delegate method still shows the button when a file is selected. The implementation of USDZ + QLPreviewController we're using is pretty basic. Is there a way around this issue?
func numberOfPreviewItems(in controller: QLPreviewController) -> Int {
return 1
}
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
let url = Bundle.main.url(forResource: models[selectedObject], withExtension: "usdz")! controller.navigationItem.rirButtonItems = nil.
// <- no effect return url as QLPreviewItem
}
#IBAction func userDidSelectARExperience(_ sender: Any) {
let previewController = QLPreviewController()
previewController.dataSource = self
previewController.delegate = self
present(previewController, animated: true)
}
This is the official answer from Apple.
Use ARQuickLookPreviewItem instead of QLPreviewItem. And set its canonicalWebPageURL to a URL (can be any URL).
func previewController(_ controller: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
guard let path = Bundle.main.path(forResource: "Experience", ofType: "usdz") else { fatalError("Couldn't find the supported input file.") }
let url = URL(fileURLWithPath: path)
if #available(iOS 13.0, *) {
let item = ARQuickLookPreviewItem(fileAt: url)
item.canonicalWebPageURL = URL(string: "http://www.google.com")
return item
} else { }
return url as QLPreviewItem
}
The version check is optional.
My approach is to add the QLPreviewController as an subview.
container is an UIView in storyboard.
let preview = QLPreviewController()
preview.dataSource = self
preview.view.frame = CGRect(origin: CGPoint(x: 0, y: -45), size: CGSize(width: container.frame.size.width, height: container.frame.size.height+45) )
container.addSubview(preview.view)
preview.didMove(toParent: self)
The y offset of the frame's origin and size may vary. This will ensure the AR QuickLook view to be the same size as the UIView, and hide the buttons (unfortunately, all of them) at the same time.
Instead of returning QLPreviewItem, use ARQuickLookPreviewItem which conforms to this protocol.
https://developer.apple.com/documentation/arkit/arquicklookpreviewitem
Then, assign a url that you would want to share (that will appear in share sheet) in canonicalWebPageURL property. By default, this property shares the file url (in this case, the USDZ file url). Doing so would not expose your file URL(s).
TLDR: I don't think you can.
I haven't seen any of the WWDC session even mention this and I can't seem to find any supporting developer documentation. I'm pretty sure the point of the ARKit QLPreviewController is so you don't have to do any actual coding on the AR side. I can see the appeal for this and for customisation in general, however, I'd suggest instead looking at some of the other ARKit projects that Apple has released and attempting to re-create those from the ground up as opposed to stripping this apart.
Please advise if this changes as I'd like to do something similar, especially within Safari.
I couldn't get to the share button at all to hide or disable it. Spent days to overcome this. I did rather unprofessional way of overcoming it. Subview QLPreviewController to a ViewController and subview a button or view on top of image view on top of share button and setting my company logo as image. It will be there all the time, even the top bar hides on full screen in AR mode. Not a clean solution. But works.
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.
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