how to call MKTileOverlay "url" function from subclass? getting EXC_BAD_INSTRUCTION - swift

I am getting a runtime error at the point marked below? How to call MKTileOverlay "url" function from subclass? getting EXC_BAD_INSTRUCTION?
Basically want to show custom tiles in some places, but when not available drop back to standard Apple map tiles.
class GCMapOverlay : MKTileOverlay {
override func url(forTilePath path: MKTileOverlayPath) -> URL {
// Get local custom map tile if available
let optionalUrl = Bundle.main.url(
forResource: "\(path.y)",
withExtension: "png",
subdirectory: "tiles/\(path.z)/\(path.x)",
localization: nil)
NSLog("tiles/\(path.z)/\(path.x)/\(path.y)")
guard let url = optionalUrl else {
// Local tile not available - want to drop back to an apple maps tile (as if MKTileOverlay wasn't subclassed)
return super.url(forTilePath: path) // <== RUNTIME ERROR: NetworkLoad (10): EXC_BAD_INSTRUCTION
}
// Local tile available so return
return url
}
}
in my controller
func setupTileRenderer() {
let overlay = GCMapOverlay()
overlay.canReplaceMapContent = true
mapView.addOverlay(overlay, level: .aboveLabels)
tileRenderer = MKTileOverlayRenderer(tileOverlay: overlay)
}

Remove overlay.canReplaceMapContent = true and change your guard statement so it loads a clear 256x256 tile.
guard let url = optionalUrl else {
return Bundle.main.url(forResource: "emptyTile", withExtension: "png")!
}
Since all of your custom tiles are already stored locally they will be immediately loaded over the default Apple tiles which makes canReplaceMapContent unnecessary.
Make sure your custom tiles don't have alpha in them otherwise the Apple tile will be visible below.

I've never used MKTileOverlay but the documentation for url(forTilePath:) states:
The default implementation of this method uses the template string you provided at initialization time to build a URL to the specified tile image.
And the MKTileOverlay class provides the initializer:
init(urlTemplate:)
But when you create an instance of GCMapOverlay, you don't use that initializer.
Replacing:
let overlay = GCMapOverlay()
with:
let overlay = GCMapOverlay(urlTemplate: someAppropriateTemplate)
or overriding the urlTemplate property in your subclass should resolve your issue when calling super.url(forTilePath:).

Related

Is it possible to share story to instagram and copy text simultaneously (iOS, swift)

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.

usdz object is not moving while loaded with SCNReferenceNode

I was following Apple Documentation and example project to load 3d Object using .SCN file with Virtual Object (subclass of SCNReferenceNode) class but suddenly i needed to change the model from .scn to usdz . Now my usdz object is loading successfully but it is not on surface (midway in the air) and i can't interact with it like (tap , pan , rotate) ... Is there any other way to get interaction with usdz object and how can I place it on the surface like I was doing it before with .scn file
For getting model URL (downloaded from server)
static let loadDownloadedModel : VirtualObject = {
let downloadedScenePath = getDocumentsDirectory().appendingPathComponent("\(Api.Params.inputModelName).usdz")
return VirtualObject(url: downloadedScenePath)!
}()
Loading it from URL
func loadVirtualObject(_ object: VirtualObject, loadedHandler: #escaping (VirtualObject) -> Void) {
isLoading = true
loadedObjects.append(object)
// Load the content asynchronously.
DispatchQueue.global(qos: .userInitiated).async {
object.reset()
object.load()
self.isLoading = false
loadedHandler(object)
}
}
Placing in the scene
func placeObjectOnFocusSquare() {
virtualObjectLoader.loadVirtualObject(VirtualObject.loadDownloadedModel) { (loadedObject) in
DispatchQueue.main.async {
self.placeVirtualObject(loadedObject)
self.setupBottomButtons(isSelected: true)
}
}
}
func placeVirtualObject(_ virtualObject: VirtualObject) {
guard let cameraTransform = session.currentFrame?.camera.transform,
let focusSquarePosition = focusSquare.lastPosition else {
statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.")
return
}
Api.Params.selectedModel = virtualObject
virtualObject.name = String(Api.Params.inputPreviewId)
virtualObject.scale = SCNVector3(Api.Params.modelXAxis, Api.Params.modelYAxis, Api.Params.modelZAxis)
virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false)
updateQueue.async {
self.sceneView.scene.rootNode.addChildNode(virtualObject)
}
}
.usdz object in sceneview
After so many tries , finally i found out that dynamic scaling of the model causing problem , Reference to this
iOS ARKit: Large size object always appears to move with the change in the position of the device camera
I scaled the object to 0.01 for all the axis (x,y and z)
virtualObject.scale = SCNVector3Make(0.01, 0.01, 0.01)

Can't hide share button in USDZ + QLPreviewController

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.

Detected missing constraints for NSTextView

I have created a Document-based application that should save an NSAttributedString with a image into a package. I ran the application and added a image to the text view and saved it. When I opened the file, a dialog box said "the document 'x' could not be opened" and this was printed to the console:
[Layout] Detected missing constraints for <NSTextField: 0x100b3f480>. It cannot be placed because there are not enough constraints to fully
define the size and origin. Add the missing constraints, or set
translatesAutoresizingMaskIntoConstraints=YES and constraints will be generated for you. If this view is laid out manually on macOS 10.12 and later,
you may choose to not call [super layout] from your override. Set a breakpoint on DETECTED_MISSING_CONSTRAINTS to debug. This error will only be logged once.
Document.swift:
enum CookRecipesFileNames : String {
case notes = "Notes.rtfd"
}
class Document: NSDocument {
var documentFileWrapper = FileWrapper(directoryWithFileWrappers: [:])
var popover : NSPopover?
var notes : NSAttributedString = NSAttributedString()
...
override class func autosavesInPlace() -> Bool {
return true
}
override func fileWrapper(ofType typeName: String) throws -> FileWrapper {
let notesRTFdata = try self.notes.data(from: NSRange(0..<self.notes.length), documentAttributes: [NSDocumentTypeDocumentAttribute: NSRTFDTextDocumentType])
if let oldTextFileWrapper = self.documentFileWrapper.fileWrappers?[CookRecipesFileNames.notes.rawValue] {
self.documentFileWrapper.removeFileWrapper(oldTextFileWrapper)
}
self.documentFileWrapper.addRegularFile(withContents: notesRTFdata, preferredFilename: CookRecipesFileNames.notes.rawValue)
return self.documentFileWrapper
}
override func read(from fileWrapper: FileWrapper, ofType typeName: String) throws {
guard let documentNotesData = fileWrappers[CookRecipesFileNames.notes.rawValue]?.regularFileContents else {
throw err(.cannotLoadNotes)
}
guard let documentNotes = NSAttributedString(rtfd: documentNotesData, documentAttributes: nil) else {
throw err(.cannotLoadNotes)
}
self.documentFileWrapper = fileWrapper
self.notes = documentNotes
}
}
Any help would be appreciated!
The warning 'Detected missing constraints...' is a Build Warning, telling you that you have added constraints to a view, but not enough to determine x and y coords, and height and width for the view. If you add no constraints in IB (an option here is to remove them all), Xcode will tell the app to use the exact position and dimensions used in IB. If you add ANY constraints to a view, you must add enough to determine both coordinates and both dimensions. If you want to keep your constraints (this is the second option), go to IB, and look for the yellow or red warning error, at the top left:
This will give you a list of missing and conflicting restraints in the View Controller.

How do I draw something on a PDF in Swift?

Here is what I am doing currently.
First, I get the document. Next I create a PDF View and assign the document to the view. Then I create a view and do my drawing in the view, then add the view to the PDFView as a subview. Then I convert the PDFView to data, then to PDF document. Since I'm doing the drawing on the first page of the PDF, I remove the first page from the original PDF, insert the new first page, and return the document.
guard let document = PDFDocument(url: url) else { return nil }
guard let page = document.page(at: 0) else { return nil }
let pageBounds = page.bounds(for: .mediaBox)
let pdfView = PDFView(frame: pageBounds)
pdfView.document = document
pdfView.enclosingScrollView?.autohidesScrollers = true
let view = /// code for subview that I am drawing on
pdfView.addSubview(view)
let data = pdfView.dataWithPDF(inside: pageBounds)
guard let pdf = PDFDocument(data: data) else { return nil }
document.removePage(at: 0)
document.insert(pdf.page(at: 0)!, at: 0)
return document
Is there a better way to do this? To add a wrinkle, my final product has a weird scroll bar image (see Screenshot). I tried adding auto hide scrollers & enclosingScrollView?.hasVerticalScroller = false but neither seem to hide the scroll bar.
Thanks in advance!
So I've solved my own problem. For anyone else stuck like me, here is what I did. For example to draw a box on a page:
create a CGContext (ctx) for PDFDocuments. You can do this either with data or with a URL you want to write to.
create a CGPDFDocument with the document you want to edit
get the CGPage of the CGPDF you want to edit (cgPage)
and:
ctx?.beginPDFPage(nil)
ctx?.drawPDFPage(cgPage)
ctx?.beginPath()
let path = CGPath(roundedRect: box as CGRect, cornerWidth: 5, cornerHeight: 5, transform: nil)
ctx?.setStrokeColor(CGColor.black)
ctx?.setFillColor(color.cgColor)
ctx?.setLineWidth(10.0)
ctx?.addPath(path)
ctx?.strokePath()
ctx?.addPath(path)
ctx?.fillPath()
ctx?.endPDFPage()
ctx?.closePDF()
(If you created the context with a URL, close will write to disk. Otherwise you'll have to do something with the PDF data.)
A simple solution is demonstrated in the 2017 WWDC video on PDFKit. I assume you have an existing document to which you want to add some drawing. Here's what to do:
Give the document a delegate.
In the delegate, implement classForPage to declare a class for your pages: that class should be your own PDFPage subclass.
In your PDFPage subclass, implement draw(with:to:). You are given a context; draw into it! Be sure to call super if you want the default drawing to happen.
If your PDFPage subclass needs to draw different things on different pages, it can find out where it is in the document by asking for self.document?.index(for:self).