SwiftUI sharing application data using pasteboard - swiftui

I have a phone / iPad application. It would be useful to export text, images, or other data to other apps or other devices.
I can export the colour data as text using UIpasteboard.general.string. This works nicely. The Universal pasteboard lets me paste into other devices.
I can export an image using UIpasteboard.general.image. I have a PNG image in my bundle resources. I can the paste it into Preview or some other image viewer. I would like to be able to paste it as a file but I can't see how to do that at the other end.
I have also a PDF manual in the Bundle. It would be nice to export that too. Like the previous image, it would be nice to paste it as a file but I cannot see how this is done.
Before I get into the code, is UIpasteboard the right way to do this? Or is there some other approach I ought to be using rather than trying to fix this. What do other apps do?
Grid(horizontalSpacing: 3, verticalSpacing:3) {
GridRow {
Text("Text: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
let pasteboard = UIPasteboard.general
pasteboard.string = toString(sw: sd.mySwatch)
} label: {
Text("Current swatch").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("Text: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
var reply: String = ""
for sw in sd.swatches {
reply = reply + toString(sw: sw)
}
UIPasteboard.general.string = reply
} label: {
Text("All swatches").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("Image: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
UIPasteboard.general.image = UIImage(contentsOfFile: Bundle.main.path(forResource: "Macbeth", ofType: "png")!)
} label: {
Text("Macbeth.png").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
GridRow {
Text("PDF: ").gridColumnAlignment(.trailing).foregroundColor(.gray)
Button {
UIPasteboard.general.url = Bundle.main.url(forResource: "ByEye", withExtension: "pdf")!
} label: {
Text("ByEye.pdf").lineLimit(1).frame(width:buttonW)
}.buttonStyle(.bordered)
}
}
That is my code. The first three options work. The last one does not because the URL is a private one for the application. I might be able to copy the file to a public directory so the URL worked but that seems like the wrong way to go about it.
Postscript:
This actually works well enough for now. I can copy stuff to the pasteboard, then paste it into an e-mail or other message to myself. This lets me get stuff out of the app without creating temporary files the I have to clean up.
I did not find a solution for the PDF or other files. I was hoping for something like this...
UIPasteboard.general.file = UIImage(contentsOfFile: Bundle.main.path(forResource: "manual", ofType: "pdf")!)
...but nothing like that exists as far as I can tell.

Related

Swiftui list: trigger an action on tap and mimic basic select then auto-unselect

I'm starting to learn swiftui and I've run into a problem that is both very basic and easily solvable in UIKit; but after spending days searching the internet and watching WWDC videos I've found no native solution.
The premise is simple: I have an array of songs I want to display in a list; when a user taps on a song view it should highlight the view on press, unhighlight after release, and then play the song (ie trigger an action). Sounds simple right?
Here's what I tried and spent way too much time on:
Using List(selection) + .onEvent(changed): I end up with a UUID (because i've only gotten selection to work with a UUID) that I then have to check against an array of songs to match AND the cell won't unhighlight/select itself; even when I try to manually set the State variable to nil or another generated UUID.
Using .onTap (either on or in the cell): I have to tap on the text of the cell to trigger onTap so I get a lot of taps that just don't work (because I have lots of white space in the cell). I also don't get a nice UI color change on press/release.
So after spending hours trying many different things I've finally come up with a solution and I basically wanted to create an account and share it to hopefully help other developers in my position. Because this so very annoyed me that something so basic took so much effort and time to do.
In the end the best solution I came up with was this:
Using ZStack and an empty button:
edit: I found I need to include and hide the content otherwise the button doesn't grow to fill the space (seems in lists it does for some reason). Though not sure what the hit on performance is of rendering the content twice when hiding it. Maybe a GeometryReader would work better?
struct SelectionView: ViewModifier {
let onSelect: () -> Void
func body(content: Content) -> some View {
ZStack (alignment: .leading) {
Button {
onSelect()
} label: {
content
.hidden()
}
content
}
}
}
extension View {
func onSelection(_ selection: #escaping () -> Void) -> some View {
self.modifier(SelectionView(onSelect: selection))
}
}
then to use it:
SongCell(song: song)
.onSelection {
// Do whatever action you want
}
No messing around with list selection, no weird tap hit boxes, and get the press/release color change. Basically put an empty button in a ZStack and trigger off it's action. Could possibly cause tap/touch issues with more complicated cells (?) but it does exactly what I need it to do for my basic app. I'm just not sure why it took so much effort and why apple doesn't support such a basic use case by default? If I've overlooked something native please do inform me. Thanks.
I got the basic idea what you are trying to do. I'm Going to show simple example. Maybe using this you will be able to find proper solution.
First let's create a color : -
#State var colorToShow : Color = Color.blue
Now in body we have our ZStack or Your cell that we want to deal with : -
ZStack{
colorToShow
}.frame(width: 50, height: 50).padding()
.onLongPressGesture(minimumDuration: 3) {
print("Process Complete")
colorToShow = .green
} onPressingChanged: { pressing in
if pressing {
print("Pressing")
colorToShow = .red
} else {
print("Pressing Released")
colorToShow = .blue
}
}
Here we are using .onLongPressGesture. You can set minimum duration on which you want to perform action. Now on process completion You set what you want to do. OnPressingChange give you a bool value that changes according to user is pressing that button or not. Show color change(Highlight) or do action while bool value is true. When user release button do action or unhighlight since bool value turns false.
Hope you find it useful.

SwiftUI Link Long Text Alignment Multiline

I'm using SwiftUI's Link to open Safari from the application. But I have a long text for the link.
What is the problem
For now, the second line of the text always keeps aligned at the center.
What I want
I want to be able to use leading TextAlignment with it.
So I've tried to use multilineTextAlignment but didn't work.
Code
Link("Some long text even very looong even that long text here!", destination: URL(string: "https://www.apple.com/")!)
.multilineTextAlignment(.leading)
Need help.
Solution
My solution was using another signature of the Link itself with multilineTextAlignment.
Link(destination: URL(string: "https://www.apple.com/")!) {
Text("Some long text even very looong even that long text here!")
.multilineTextAlignment(.leading)
}
From Apple Documentation
public struct Link<Label> : View where Label : View {
/// Creates a control, consisting of a URL and a label, used to navigate
/// to the given URL.
///
/// - Parameters:
/// - destination: The URL for the link.
/// - label: A view that describes the destination of URL.
public init(destination: URL, #ViewBuilder label: () -> Label)
Hope will help someone else!
Best

Show downloaded mp4 inside a view like VStack and play it AVPlayer, SwiftUI

I'm working on the offline downloads section for a video streaming app. Currently, I'm able to download videos and also can see the file path for the downloads as follows:
Button(action: {
let fileManager = FileManager.default
do {
let resourceKeys : [URLResourceKey] = [.creationDateKey, .isDirectoryKey]
let documentsURL = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let enumerator = FileManager.default.enumerator(at: documentsURL,
includingPropertiesForKeys: resourceKeys,
options: [.skipsHiddenFiles], errorHandler: { (url, error) -> Bool in
print("directoryEnumerator error at \(url): ", error)
return true
})!
for case let fileURL as URL in enumerator {
let resourceValues = try fileURL.resourceValues(forKeys: Set(resourceKeys))
// print(fileURL.path, resourceValues.creationDate!, resourceValues.isDirectory!)
print(fileURL.path)
self.array.append(fileURL.path)
}
} catch {
print(error)
}
}){
Text("Check downloads")
}
//response when button is clicked
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/Bz75srG5nctDCnAWIspM.mp4
/private/var/mobile/Containers/Data/Application/AD90B554-3465-43DD-BAE2-04BC83F850A3/Documents/GWYgrgDVYaowxeGcOjzp.mp4
Since I've renamed the mp4 files using movie ID's (eg: GWYgrgDVYaowxeGcOjzp.mp4) my goal is to use this name to show which videos were downloaded and allow users to play the video. I'm able to do the name processing later on, but how am I able to get the downloaded mp4 files into a view like a VStack? I was thinking of looping through and adding the files into an array but wanted to check if there's a different way. Also, would be great if you could show me how to play a video using the path as the url. I've tried applying suggested methods but ended up with a "NSURLConnection finished with error - code -1002" error. Thank you!

Posting with UIActivityViewController, but avoiding cropping?

Say you construct an image that is fullscreen on different devices. You then use UIActivityViewController to post to - for example - Instagram in the normal way.
The user clicks your share button, it brings up the usual iOS-sharing-thingy,
and you can post to Instagram (assuming the user's an Instagram user of course). No worries.
But typically the image is cropped on Instagram - you lose a little of the top and bottom.
Is there actually any solution for this?
Note that indeed - say you open the normal Photos app on the iPhone, and "share" and post on Instagram ... you lose a little of the top and bottom!
When the user does click the Instagram icon on this ...
in fact is there a way for me then to go back, be aware of the user's choice, and make the image the appropriate size?
Is there perhaps a way to pass a selection of images (various sizes) to the UIActivityViewController?
What's the deal on this, it seems like a basic failing?
Note - I'm fully aware that BEFORE going to the iOS-share-thingy, I could ask the user myself "What size image would you like me to make?"
Note - I'm aware that it's in some cases possible to post "directly" to say Instagram inside the app, without using Apple's share system; that's lame though.
To save anyone typing, here's some clean code to bring up the iOS-share system...
#IBAction func userClickedOurShareButton()
{
let s:[AnyObject] = [buildImage()]
let ac = CleanerActivity(activityItems:s, applicationActivities:nil)
ac.popoverPresentationController?.sourceView = view
// needed so that iPads won't crash. sarcasm: thanks Apple
ac.excludedActivityTypes = [UIActivityType.assignToContact,
UIActivityType.saveToCameraRoll,
UIActivityType.addToReadingList,
UIActivityType.copyToPasteboard ]
// consider UIActivityTypeMessage also
if #available(iOS 9.0, *) {
ac.excludedActivityTypes?.append(UIActivityType.openInIBooks)
} else {
// Fallback on earlier versions
}
self.present(ac, animated:false, completion:nil)
}
class CleanerActivity: UIActivityViewController {
func _shouldExcludeActivityType(_ activity: UIActivity) -> Bool {
let activityTypesToExclude = [
"com.apple.reminders.RemindersEditorExtension",
"com.apple.mobilenotes.SharingExtension",
"com.google.Drive.ShareExtension",
"com.apple.mobileslideshow.StreamShareService"
]
if let actType = activity.activityType {
if activityTypesToExclude.contains(actType.rawValue) {
return true
}
else if super.excludedActivityTypes != nil {
return super.excludedActivityTypes!.contains(actType)
}
}
return false
}
Disclaimer: this solution involves hard-coding Instagram's extension identifier into your app, which may or may not make it through app review, and may break in the future. Try at your own risk!
Apple provides a mechanism for this called UIActivityItemProvider. Instead of passing an image to your UIActivityViewController, you can pass subclass of UIActivityItemProvider that overrides itemForActivityType to return an appropriate image based on the activity type chosen by the user.
Apple provides constants for many common activity types, but Instagram isn't yet included. You can identify Instagram by checking if the activity type's raw value is com.burbn.instagram.shareextension. This would break if Instagram changed the ID of their extension.
Here's an UIActivityItemProvider that provides different images to Instagram:
class DynamicImageProvider: UIActivityItemProvider {
let instagramImage: UIImage
let defaultImage: UIImage
init(instagramImage: UIImage, defaultImage: UIImage) {
self.instagramImage = instagramImage
self.defaultImage = defaultImage
super.init(placeholderItem: defaultImage)
}
override func activityViewController(_ activityViewController: UIActivityViewController,
itemForActivityType activityType: UIActivityType) -> Any? {
if activityType.rawValue == "com.burbn.instagram.shareextension" {
return instagramImage
}
else {
return defaultImage
}
}
}
Then change the first two lines of your IBAction:
let imageProvider = DynamicImageProvider(instagramImage:buildInstagramImage(), defaultImage:buildImage())
let ac = CleanerActivity(activityItems:[imageProvider], applicationActivities:nil)

How to use custom background image or color using Promotion

I am trying to build an iOS app using Promotion. It works fine as long
as I use the build in default design. But I want to be able to style for example
the navbar as I want (different colors and different background images).
How can I do this using Promotion?
Thanks!
You can use Teacup to easily customize your views. https://github.com/rubymotion/teacup
Add teacup to your gemfile, create a file appearence.rb in app/styles/appearence.rb and paste the following code:
Teacup::Appearance.new do
style UINavigationBar, {
tintColor: UIColor.blackColor
}
style UIBarButtonItem, {
tintColor: UIColor.blackColor
}
style UITableViewCell, {
layer: { # style the layer!
shadowRadius: 3
},
backgroundView: { # style the background!
backgroundColor: UIColor.blackColor
},
imageView: { # style the imageView!
contentMode: UIViewContentModeScaleAspectFill
}
}
style UISearchBar, {
tintColor: UIColor.colorWithRed(0.733, green:0.733, blue:0.733, alpha:1)
}
end
Add this line to your app delegate before loading your views
Teacup::Appearance.apply
Hope it helps.