ios 13 UIContextMenu shows shortened UIAction titles - ios

I decided to addUIContextMenuInteraction to my UITableViewCell, it works fine, but the title that has 9+ letters (without image) or 6+ letters(with image) is getting shortened like this:
Implementation of delegate method:
extension MyCustomCell: UIContextMenuInteractionDelegate {
#available(iOS 13.0, *)
func contextMenuInteraction(_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
return UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { _ -> UIMenu in
let first = UIAction(title: "8Letters") { _ in
print("8 letters")
}
let second = UIAction(title: "9Letters+") { _ in
print("9 letters")
}
let third = UIAction(title: "Hello", image: UIImage(systemName: "square.and.arrow.up")) { _ in
print("5 letters + image")
}
let fourth = UIAction(title: "Hello+", image: UIImage(systemName: "square.and.arrow.up")) { _ in
print("6 letters + image")
}
return UIMenu(title: "", children: [first, second, third, fourth])
}
}
}

Check if any third party framework added to your project for customising the UITableViewCell is breaking the UI. In my case, the issue is caused by third party framework ( "SkeletonView") which I had added to give shimmer effect to UITableViewCell

Related

UIContextMenuActionProvider puts unwanted checkmark icons on items

The problem
I was implementing UIContextMenuInteraction, and ended up with the behavior I can't explain or find fixes too. The issue as seen from the screen shot that menu items have checkmarks. This is not intended and those checkmarks added automatically. Ideally I'd like use SF Symbols, but any image I add ends up being this checkmark. Even if I set image to nil, it still adds this weird checkmark.
Additional steps taken: Reinstall SF Symbols and SF Pro, Clean build, Restart xCode / Simulator
Reproduced: Simulator iOS 13.3, iPhone 7 iOS 13.3
System: Catalina 10.15.1, xCode 11.3.1
Code:
import UIKit
class ViewController: UIViewController {
let sampleView = UIView(frame: CGRect(x: 50, y: 300, width: 300, height: 200))
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(sampleView)
sampleView.backgroundColor = .systemIndigo
let interaction = UIContextMenuInteraction(delegate: self)
sampleView.addInteraction(interaction)
}
}
extension ViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(
_ interaction: UIContextMenuInteraction,
configurationForMenuAtLocation location: CGPoint
) -> UIContextMenuConfiguration? {
let actionProvider: UIContextMenuActionProvider = { [weak self] _ in
let like = UIAction(
title: "Like",
image: UIImage(systemName: "heart"),
identifier: nil,
discoverabilityTitle: nil,
attributes: [],
state: .on
) { _ in
}
let copy = UIAction(
title: "Copy",
image: nil,
identifier: nil,
discoverabilityTitle: nil,
attributes: [],
state: .on
) { _ in
}
let delete = UIAction(
title: "Delete",
image: UIImage(systemName: "trash"),
identifier: nil,
discoverabilityTitle: nil,
attributes: [.destructive],
state: .on
) { _ in
}
return UIMenu(
title: "",
image: nil,
identifier: nil,
options: [],
children: [
like, copy, delete
]
)
}
let config = UIContextMenuConfiguration(identifier: nil, previewProvider: nil, actionProvider: actionProvider)
return config
}
}
You need to change UIAction.state from .on to .off to get rid of the checkmark.

iOS Contextual Menu API can't display Link Presentation content

I am attempting to display the preview, using the LinkPresentation API, of a website inside of a UIContextualMenu when the user force or long presses on a link, similar to how peek and pop used to work for links. However, the LP API loads the metadata from the website asynchronously, and when I force touch on the link, the preview controller displayed by the context menu is blank. The following is the delegate method for the UIContextMenuConfiguration:
public func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
guard let url = shouldShowContextualMenu(location: location) else { return nil }
return UIContextMenuConfiguration(identifier: nil, previewProvider: { () -> UIViewController? in
self.getMetadataForUrl(url: url ) { linkView in
self.previewController = LinkPreviewViewController(linkView: linkView)
}
return self.previewController
}, actionProvider: nil)
}

iOS 13.0 UIMenu and UIAction for UIContextMenuConfiguration

I'm trying to use the new APIs introduced in iOS 13.0 Beta. I have downloaded Xcode 11.0 Beta 3 to be able to access these API.
Some of the code I found online does things like:
extension SingleViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu<UIAction>? in
// Creating Save button
let save = UIAction(__title: "Save", image: UIImage(systemName: "tray.and.arrow.down.fill"), options: []) { action in
// Just showing some alert
self.showAlert(title: action.title)
}
// Creating Rotate button
let rotate = UIAction(__title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise"), options: []) { action in
self.showAlert(title: action.title)
}
// Creating Delete button
let delete = UIAction(__title: "Delete", image: UIImage(systemName: "trash.fill"), options: .destructive) { action in
self.showAlert(title: action.title)
}
// Creating Edit, which will open Submenu
let edit = UIMenu<UIAction>.create(title: "Edit...", children: [rotate, delete])
// Creating main context menu
return UIMenu<UIAction>.create(title: "Menu", children: [save, edit])
}
return configuration
}
}
This seems fine but doesn't even compile in my Xcode. The errors I get are:
Cannot specialize non-generic type 'UIMenu' Replace '<UIAction>' with ''
on creating configuration constant.
Type of expression is ambiguous without more context
on creating save action.
and couple other similar errors.
I should also mention, I don't even have the constructors that are in this format:
UIAction(__title: "String", image: UIImage, options: Array)
UIMenu.create(...)
Why are these missing in Xcode-11 beta 3.0 ?
Well, it changed. It's a beta! And I would expect it to change again, but for now, in beta 3, the initializer for UIAction is
init(__title title: String, image: UIImage?, identifier: UIAction.Identifier?,
handler: #escaping UIActionHandler)
UIMenu is not generic, and its initializer is
init(__title title: String, image: UIImage?, identifier: UIMenu.Identifier?,
options: UIMenu.Options = [], children: [UIMenuElement])
So here's a rewrite of your code that does compile under beta 3:
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu? in
let save = UIAction(__title: "Save", image: UIImage(systemName: "tray.and.arrow.down.fill"), identifier: nil) { action in
// whatever
}
let rotate = UIAction(__title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise"), identifier: nil) { action in
// whatever
}
let delete = UIAction(__title: "Delete", image: UIImage(systemName: "trash.fill"), identifier: nil) { action in
// whatever
}
let edit = UIMenu(__title: "Edit", image: nil, identifier: nil, children:[rotate,delete])
return UIMenu(__title: "Menu", image: nil, identifier: nil, children:[save, edit])
}
return configuration
}
In Xcode 11.0 Beta 5, you can now write:
extension SingleViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu? in
// Creating Save button
let save = UIAction(title: "Save", image: UIImage(systemName: "tray.and.arrow.down.fill")) { action in
// Just showing some alert
self.showAlert(title: action.title)
}
// Creating Rotate button
let rotate = UIAction(title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise")) { action in
self.showAlert(title: action.title)
}
// Creating Delete button
let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill"), attributes: .destructive) { action in
self.showAlert(title: action.title)
}
// Creating Edit, which will open Submenu
let edit = UIMenu(title: "Edit...", children: [rotate, delete])
// Creating main context menu
return UIMenu(title: "Menu", children: [save, edit])
}
return configuration
}
}
The classes are no longer generic e.g. UIMenu instead of UIMenu<UIAction>.
Comprehensive Swift initializers let you drop optional params, which have reasonable default values e.g. image, attributes and identifier.
No more UIMenu<UIAction>.create(...) or UIAction(__title:...). They're all real initializers now, yay!
On swift 5, this works:
extension ViewController: UIContextMenuInteractionDelegate {
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
let configuration = UIContextMenuConfiguration(identifier: nil, previewProvider: nil) { actions -> UIMenu? in
// Creating Save button
let save = UIAction(title: "Save", image: UIImage(systemName: "tray.and.arrow.down.fill")) { action in
// Just showing some alert
self.showAlert(title: action.title)
}
// Creating Rotate button
let rotate = UIAction(title: "Rotate", image: UIImage(systemName: "arrow.counterclockwise")) { action in
self.showAlert(title: action.title)
}
// Creating Delete button
let delete = UIAction(title: "Delete", image: UIImage(systemName: "trash.fill")) { action in
self.showAlert(title: action.title)
}
// Creating Edit, which will open Submenu
let edit = UIMenu(title: "Edit...", children: [rotate, delete])
// Creating main context menu
return UIMenu(title: "Menu", children: [save, edit])
}
return configuration
}
}

Why doesn't my Apple Watch complication show anything?

I created an app using Xcode's "iOS App with Watchkit App" template, went into TARGETS and checked Complications Configuration > Supported Families > Graphic Corner. I opened ComplicationController.swift in the Extension and modified getCurrentTimelineEntry():
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
let cornerTemplate = CLKComplicationTemplateGraphicCornerStackText()
cornerTemplate.outerTextProvider = CLKSimpleTextProvider(text: "Outer")
cornerTemplate.innerTextProvider = CLKSimpleTextProvider(text: "Inner")
let entry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: cornerTemplate)
handler(entry)
}
I also modified getLocalizableSampleTemplate() to provide a sample, and this is not working either:
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTemplate?) -> Void) {
let cornerTemplate = CLKComplicationTemplateGraphicCornerStackText()
cornerTemplate.outerTextProvider = CLKSimpleTextProvider(text: "Outer")
cornerTemplate.innerTextProvider = CLKSimpleTextProvider(text: "Inner")
handler(cornerTemplate)
}
When I run the app in the simulator or on my phone/watch and select the complication as one of the graphic corners, I expect to see "Outer" and "Inner". Instead it shows the name of my app for one and "---" for the other.
What am I doing wrong?
This is some of my code that is currently working:
var graphicCornerComplication: CLKComplicationTimelineEntry? {
guard #available(watchOSApplicationExtension 5.0, *) else {
return nil
}
let innerTextProvider = CLKSimpleTextProvider(text: "Inner")
let outerTextProvider = CLKSimpleTextProvider(text: "Outer")
let template = CLKComplicationTemplateGraphicCornerStackText()
template.outerTextProvider = outerTextProvider
template.innerTextProvider = innerTextProvider
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template)
return timelineEntry
}
A few considerations:
Have you implemented your getLocalizableSampleTemplate code? This should be the first thing you do when configuring complications. You should have something ready to show immediately when users scroll through complication slots and see yours. If you don't, that could be why you're seeing the dashes instead of your intended text.
Is your complication data source correctly assigned? Under Targets > Your WatchKit Extension > Complications Configuration > Data Source Class, make sure ComplicationController is assigned.
Your entry could be coming up nil if you're working on an older version of WatchOS.
EDIT - To clarify, graphicCornerComplication is just a property that I have added to some of my models so that I can quickly get a timeline entry by just calling graphicCornerComplication on them. In use, it looks something like this:
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: #escaping (CLKComplicationTimelineEntry?) -> Void) {
switch complication.family {
case .graphicCorner:
let graphicCornerComplication = dataModel.graphicCornerComplication
handler(graphicCornerComplication)
default:
handler(nil)
}
}

How to add tap functionality to label using UITapGestureRecognizer in static func

I need to add tap functionality to labels (to open a website) in my app dynamically, I was thinking in create static function inside a class. I want to launch the function in any ViewController of my app.
I´ve done this using Swift 3:
class Launcher {
static func openWeb(label: UILabel, url: String) {
func tapFunction(sender:UITapGestureRecognizer) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: url)!)
}
}
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
label.isUserInteractionEnabled = true
label.addGestureRecognizer(tap)
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: url)!)
}
}
}
But doesn't work because I'm getting error in action: #selector(tapFunction)
error: Argument of '#selector' cannot refer to local function 'tapFunction(sender:)'
If I use this inside a ViewController code using action: #selector(myView.tapFunction) like the following, works
Inside viewDidLoad
let tap = UITapGestureRecognizer(target: self, action: #selector(MyViewController.tapFunction))
mylabel.isUserInteractionEnabled = true
mylabel.addGestureRecognizer(tap)
Separated function inside ViewController
func tapFunction(sender:UITapGestureRecognizer) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: url)!)
}
}
I want to convert this last code to static function inside my Class to call it in any ViewController. Thank you
Try resolve it using extension
extension UILabel {
func openWeb(url: String) {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapFunction))
self.isUserInteractionEnabled = true
self.addGestureRecognizer(tap)
openURL(url: url)
}
func tapFunction(sender:UITapGestureRecognizer) {
openURL(url: "")
}
func openURL(url: String) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(URL(string: url)!, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(URL(string: url)!)
}
}
}
let label = UILabel()
label.openWeb(url: "123")
To store link into label you can use associations with label object.
A method that is supposed to communicate with an instance cannot be a static / class method. That's what static / class method means; it is about the class — there is no instance in the story. Thus what you are proposing is impossible, unless you hand the static method the instance it is supposed to talk to.
To me personally, a tappable URL-opening label sounds like a label subclass, and this functionality would then be an instance method of that label.
First, there are some general problems with your approach. Selectors work by message passing. E.g. with UITapGestureRecognizer(target:, action:) The message calling the method (the action) is sent to the variable ( the target).
When you create a local function, that function is a member of the enclosing function and not the containing class or instance, so your approach categorically cannot work.
Even if it could work, it would also go against OOP and MVC design principals. A Label should not be in charge of what happens when it's tapped, just as the title of a book is not in charge of opening the book.
Taking all that into consideration, this is how I would solve your problem:
extension UIViewController {
func addTapGesture(to view: UIView, with selector: Selector) {
view.isUserInteractionEnabled = true
view.addGestureRecognizer(UITapGestureRecognizer(target: view, action: selector))
}
}
extension UIApplication {
func open(urlString: String) {
if #available(iOS 10.0, *) {
open(URL(string: urlString)!, options: [:], completionHandler: nil)
} else {
openURL(URL(string: urlString)!)
}
}
}
class YourVC: UIViewController {
var aLabel: UILabel? = nil
func addTapGesture() {
addTapGesture(to: aLabel!, with: #selector(tapGestureActivated))
}
func tapGestureActivated(gesture: UITapGestureRecognizer?) {
UIApplication.shared.open(urlString: "YourURLHere")
}
}
This abstracts away the boilerplate and is simple at the point of use, while still properly separating out Type responsibilities and using generalized functionality that can be re-used elsewhere.

Resources