Share Extension - Remove Textfield - ios

Is there a way to remove the TextView- and PreviewPart, marked in RED, from my ShareExtention?
Im using the default:
class ShareViewController: SLComposeServiceViewController
As ViewController
I want to just show the Destination Selector like below (I know how to create that)
I know that you can create your own ViewController but it would be very hard to rebuild the default one.
The Second thing is:
What method would you recommend to Transfer/Copy Data/Files and which to read the Main App's Document Directory?
I want to transfer/copy the selected Document to the DocumentsDirectory of my Main App. Both are in the same AppGroup. Bc if I just save the Url of the current Document in UserDefault, then I guess the Main App can't access it.
I need to read the Main App's Document Directory, because I need the file Hierachy so I can select the saving location.
Note: Just because there was some confusion in the comments for whatever reason: This is my own ShareExtention and not smo else's in UIActivityController

Not the best, but here's what I've got based on the docs:
override func viewDidLoad() {
super.viewDidLoad()
textView.isHidden = true
}
But then there's blank space. Perhaps it's better to put some placeholder text in and prevent user input. There are other options in the docs you can use for that, or you can mess with textView (which is a UITextView)...
Edit: Here's a complete example that shows the URL in the text field, grey and non-user-interactible.
class ShareViewController: SLComposeServiceViewController {
override func viewDidLoad() {
super.viewDidLoad()
textView.isUserInteractionEnabled = false
textView.textColor = UIColor(white: 0.5, alpha: 1)
textView.tintColor = UIColor.clear // TODO hack to disable cursor
getUrl { (url: URL?) in
if let url = url {
DispatchQueue.main.async {
// TODO this is also hacky
self.textView.text = "\(url)"
}
}
}
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
func getUrl(callback: #escaping ((URL?) -> ())) {
if let item = extensionContext?.inputItems.first as? NSExtensionItem,
let itemProvider = item.attachments?.first as? NSItemProvider,
itemProvider.hasItemConformingToTypeIdentifier("public.url") {
itemProvider.loadItem(forTypeIdentifier: "public.url", options: nil) { (url, error) in
if let shareURL = url as? URL {
callback(shareURL)
}
}
}
callback(nil)
}
override func didSelectPost() {
getUrl { (url: URL?) in
if let url = url {
DispatchQueue.main.async {
print("url: \(url)")
}
}
}
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}

What worked for me is to use a custom solution instead of SLComposeServiceViewController. It looks the same, and can add any element I want (see image at the end and here's the code at the commit when it was implemented).
Steps:
(code) Change ShareViewController to simple UIViewController
(code) Add blur effect to ShareViewController
(storyboard) Add container view to ShareViewController
(storyboard) Add navigation controller
(storyboard) Embed navigation controller in ShareViewController's container view
Customize the view controllers in the navigation controller (see this SO thread for example)
Step 1. Change ShareViewController to simple UIViewController
import UIKit
class ShareViewController: UIViewController {
// ^^^^^^^^^^^^^^^^
Step 2. Add blur effect to ShareViewController
// ShareViewController continued from Step 1.
override func viewDidLoad() {
super.viewDidLoad()
// https://stackoverflow.com/questions/17041669/creating-a-blurring-overlay-view/25706250
// only apply the blur if the user hasn't disabled transparency effects
if UIAccessibilityIsReduceTransparencyEnabled() == false {
view.backgroundColor = .clear
let blurEffect = UIBlurEffect(style: .dark)
let blurEffectView = UIVisualEffectView(effect: blurEffect)
//always fill the view
blurEffectView.frame = self.view.bounds
blurEffectView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
view.insertSubview(blurEffectView, at: 0)
} else {
view.backgroundColor = .black
}
// Do any additional setup after loading the view.
}
Step 3. Add container view to ShareViewController
Drag a Container View from the Object Library into the ShareViewController on the storyboard, and adjust dimension. For example:
Step 4. Add navigation controller
Drag a Navigation Controller from the Object Library to the storyboard.
Step 5. Embed navigation controller in ShareViewController's container view
Control-drag from the container view of ShareViewController to the navigation controller, select "Embed" from the menu. Should look similar to this:
Step 6. Customize the view controllers in the navigation controller (see this SO thread for example)
My result:

You can't.
When you use the share extension you will afterwards select an app to share to, thus plugging yourself to the share extension of said app.
Your app does not and cannot have any access of what said share extension does, those are totally separated from the main app runtime.
Also this allow users to customise what they share.
For the second part of your post you need to elaborate, who wants to transfer a document, you as the developper or your end-user?

Related

Should we be using UIView or UIViewController for the following feature?

We have the following app, where user can switch to different "page" (purple, yellow, ... colours) from side menu.
I was wondering, should the "page" be implemented as UIView, or should the "page" be implemented as UIViewController?
The pages shall responsible to
Read/ write from/ to CoreData.
Possible holding a UIPageView, which user can swipe through multiple child pages as shown in https://i.stack.imgur.com/v0oNo.gif
Holding a UICollectionView.
User can drag and move the items in the UICollectionView
User can perform various contextual action (Delete, clone, ...) on the items in UICollectionView.
Can easily port to iPad in the future.
Currently, my implementation of using UIView are as follow.
private func archive() {
if let trashView = self.trashView {
trashView.removeFromSuperview()
self.trashView = nil
}
if self.archiveView != nil {
return
}
let archiveView = ArchiveView.instanceFromNib()
self.view.addSubview(archiveView)
archiveView.translatesAutoresizingMaskIntoConstraints = false
archiveView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
archiveView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
archiveView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
archiveView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.archiveView = archiveView
}
private func trash() {
if let archiveView = self.archiveView {
archiveView.removeFromSuperview()
self.archiveView = nil
}
if self.trashView != nil {
return
}
let trashView = TrashView.instanceFromNib()
self.view.addSubview(trashView)
trashView.translatesAutoresizingMaskIntoConstraints = false
trashView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
trashView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
trashView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
trashView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
self.trashView = trashView
}
I notice that if I implement the "pages" using UIView, I will lost some capability of UIViewController like
viewDidLoad callback.
viewWillLoad callback.
viewDidLayoutSubviews callback.
...
However, I am not clear whether losing those capabilities will stop me from implementing a proper "page"?
May I know, should I implement those "pages" using UIView, or using UIViewController?
I would do this with UIViewController because of all the UIKit callback reasons you listed in the question already.
I assume that you have a UINavigationController instance that's set as window.rootViewController for your app. You have a reference to this instance using which you can easily switch between different screens.
Example
class SlideMenuViewController: UIViewController {
enum Option {
case archive
case trash
}
var onSelect: ((_ option: Option) -> Void)?
}
class ArchiveViewController: UIViewController {}
class TrashViewController: UIViewController {}
class AppNavigator {
let mainNavigationController: UINavigationController
init(navigationController: UINavigationController) {
self.mainNavigationController = navigationController
}
private lazy var slideMenuVC: SlideMenuViewController = {
let slideMenu = SlideMenuViewController()
slideMenu.onSelect = { [weak self] (option) in
self?.openScreen(for: option)
}
return slideMenu
}()
private lazy var archiveVC: ArchiveViewController = {
return ArchiveViewController()
}()
private lazy var trashVC: TrashViewController = {
return TrashViewController()
}()
func openScreen(for option: SlideMenuViewController.Option) {
let targetVC: UIViewController
switch option {
case .archive: targetVC = archiveVC
case .trash: targetVC = trashVC
}
mainNavigationController.setViewControllers([targetVC], animated: true)
}
}
Perhaps I totally misunderstood your question, but your UIView should only hold logic related to how the view itself will appear to the user. The UIView should not contain any logic related to the other views or to the model.
UIKit assumes that you use the MVC model to implement apps on the Apple platforms. This means that any code related to controlling which view has to appear and which data the view should get from the model, should be written in the ViewController.
In Xcode you have both the UICollectionViewController and the UIPageViewController to implement page swipes and dragging an dropping views.
View controllers can give the control to other view controllers to present views. The view controller also determine the data that should be presented by the view. Please, check out this article about the MVC model.
Kind regards,
MacUserT

Multiple ViewControllers with same background

How can one still background image in multiple view controllers be implemented?
With self.view.backgroundColor = UIColor(patternImage: UIImage(named: "file.png")) background would move along with UIViewController while screen switching.
Is it possible to place image on separate layer under view controllers?
Solution 1
Create a class called MyBackground:
class MyBackground {
class func addBackgroundImage() {
UIGraphicsBeginImageContext(self.view.frame.size)
UIImage(named: "file")?.drawInRect(self.view.bounds)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.backgroundColor = UIColor(patternImage: image)
}
}
If you want to call it in your AViewController, simply call MyBackground.addBackgroundImage()
Solution 2
Create a ViewController called MyViewController:
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addBackgroundImage()
}
func addBackgroundImage() {
UIGraphicsBeginImageContext(self.view.frame.size)
UIImage(named: "file")?.drawInRect(self.view.bounds)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
view.backgroundColor = UIColor(patternImage: image)
}
}
If you AViewController want to set this image, simply do like this:
AViewController: MyViewController //AViewController is a subclass of MyViewController
For example: I have an image call background, and I will set it as a background image of my ViewController:
If I just add it in one line as #startupthekid said:
If I add it as I said:
i think you can set background in your container view controllers, such as navi controller, tab controller, then set all your view controller's background to clear color.
A better answer is to use UIAppearance. If you're not familiar with it, UIAppearance is a proxy protocol that you send messages to and it modifies later instances of that class.
For example if I have a custom button class RoundButton:
RoundButton.appearance().titleFont = Font.Heavy(20)
And in my RoundButton class:
dynamic var titleFont: UIFont? {
get { return self.titleLabel?.font }
set { self.titleLabel?.font = newValue }
}
The dynamic keywords is incredibly important. It's used for objective-c compatibility and setting properties via UIAppearance that don't use it will cause a crash.
You can do as many custom properties as you want and there's a bunch already default in the os.
In your case you can do something like:
UIImageView.appearanceWhenContainedInInstancesOfClasses([MyCustomViewController.self)].customImage = UIImage(...)
You can of course get away with just a custom view controller but in my opinion, UIApperance keeps your UI code separate from your controller logic.
One way you could implement the behavior you want is a UIViewController subclass:
class MyCustomViewController: UIViewController {
private let backgroundImageView = UIImageView()
dynamic var backgroundImage: UIImage? {
get { self.backgroundImageView.image }
set { self.backgroundImageView.image = newValue }
}
}
and then modify the backgroundImage property on the appearance instance of MyCustomViewController.
MyCustomViewController.appearance().backgroundImage = ...
EDIT: Given that your question was misleading and that you instead want a shared image that never moves throughout all your view controllers, you can simply insert an image view into the application window:
if let window = UIApplication.sharedApplication().windows.first {
let imageView = UIImageView(image: UIImage(named: "face-mask"))
window.addSubview(imageView)
imageView.frame = window.bounds
}
If you like Autolayout, like I do, then I'd set constraints to pin the image view to the window edges.
you should set the image in viewDidload of every viewController
Update :
either you should set in every view controller or you can set to windows . Second thing if your background changes dynamically with different images then you can use prepareforsegue method to pass images to nextviewcontroller if images are different for each viewcontroller. if same image for every viewcontroller then you can set it to window.
:)

Make UILabel focusable and tappable (tvOS)

I'm trying to implement 6 lines high description label and I want it to be focusable. Ideally that would mean extending UILabel class to make a custom component. I tried that by implementing canBecomeFocused and didUpdateFocusInContext but my UILabel doesn't seem to get any focus.
I also tried replacing UILabel with UIButton, but buttons aren't really optimised for this sort of thing. Also that would mean I'd need to change buttonType on focus from custom to plain.. and buttonType seems to be a ready-only property.
In reality I'd like to have exact same text label implemented by Apple in Apple TV Movies app. For movie description they have a text label that displays a few lines of text and a "more". When focused it looks like a button (shadows around) and changed background color. When tapped - it opens up a modal window with entire movie description.
Any suggestions? Or maybe someone has already implemented this custom control for tvOS? Or event better - there is a component from Apple that does this and I'm missing something.
P.S: Swift solution would be welcome :)
Ok, answering my own question :)
So it appears that some some views are "focusable" on tvOS out-of-the-box, and other have to be instructed to do so.
I finally ended up using UITextView, which has a selectable property, but if not one of these focusable views by default. Editing of TextView has to be disabled to make it look like UILabel. Also, currently there is a bug which prevents you from using selectable property from Interface Builder but works from code.
Naturally, canBecomeFocused() and didUpdateFocusInContext had to be implemented too. You'll also need to pass a UIViewController because UITextView is not capable of presenting a modal view controller. Bellow is what I ended up creating.
class FocusableText: UITextView {
var data: String?
var parentView: UIViewController?
override func awakeFromNib() {
super.awakeFromNib()
let tap = UITapGestureRecognizer(target: self, action: "tapped:")
tap.allowedPressTypes = [NSNumber(integer: UIPressType.Select.rawValue)]
self.addGestureRecognizer(tap)
}
func tapped(gesture: UITapGestureRecognizer) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let descriptionView = storyboard.instantiateViewControllerWithIdentifier("descriptionView") as? DescriptionViewController {
if let view = parentView {
if let show = show {
descriptionView.descriptionText = self.data
view.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
view.presentViewController(descriptionView, animated: true, completion: nil)
}
}
}
}
override func canBecomeFocused() -> Bool {
return true
}
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
if context.nextFocusedView == self {
coordinator.addCoordinatedAnimations({ () -> Void in
self.layer.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(0.2).CGColor
}, completion: nil)
} else if context.previouslyFocusedView == self {
coordinator.addCoordinatedAnimations({ () -> Void in
self.layer.backgroundColor = UIColor.clearColor().CGColor
}, completion: nil)
}
}
}
As for making a UILabel focusable:
class MyLabel: UILabel {
override var canBecomeFocused: Bool {
return true
}
override func didUpdateFocus(in context: UIFocusUpdateContext, with coordinator: UIFocusAnimationCoordinator) {
super.didUpdateFocus(in: context, with: coordinator)
backgroundColor = context.nextFocusedView == self ? .blue:.red
}
}
IMPORTANT!!!
As stated on the apple developer portal:
The value of this property is true if the view can become focused; false otherwise.
By default, the value of this property is false. This property informs the focus engine if a view is capable of being focused. Sometimes even if a view returns true, a view may not be focusable for the following reasons:
The view is hidden.
The view has alpha set to 0.
The view has userInteractionEnabled set to false.
The view is not currently in the view hierarchy.
Use a collection view with just one cell and add transform to cell and change cell background color in didUpdateFocusInContext when focus moves to cell.
override func didUpdateFocusInContext(context: UIFocusUpdateContext, withAnimationCoordinator coordinator: UIFocusAnimationCoordinator) {
coordinator.addCoordinatedAnimations({
if self.focused {
self.transform = CGAffineTransformMakeScale(1.01, 1.01)
self.backgroundColor = UIColor.whiteColor()
self.textLabel.textColor = .blackColor()
}
else {
self.transform = CGAffineTransformMakeScale(1, 1)
self.backgroundColor = UIColor.clearColor()
self.textLabel.textColor = .whiteColor()
}
}, completion: nil)
}
As an additional step you could try to extract the color of the image if you are using the image as background like iTunes and use that for Visual effect view behind the cell.
Also you can apply transform to the collectionView in the video controller to make it look like in focus
You can use system button, and set the background image in storyboard to an image that contains the color you would like

Slide UIInputView with UIViewController like Slack

I'd like to use the UIViewController's input accessory view like this:
override func canBecomeFirstResponder() -> Bool {
return true
}
override var inputAccessoryView: UIView! {
return self.bar
}
but the issue is that I have a drawer like view and when I slide the view open, the input view stays on the window. How can I keep the input view on the center view like Slack does it.
Where my input view stays at the bottom, taking up the full screen (the red is the input view in the image below):
There are two ways to do this exactly like Slack doing it, Meiwin has a medium post here A Stickler for Details: Implementing Sticky Input Field in iOS to show how he managed to do this which he actually puts an empty UIView as an inputAccessoryView then track it’s coordinates on screen to know where to put his custom view in relation with the empty view, this way can be helpful if you are going to support SplitViewController on iPad, but if you are not interested in this way, you can see how I managed to do this like this image
Here is before swiping
Here is after
All I did was actually taking a snapshot from the inputAccessoryView window and putting it on the NavigationController of the TableViewController
I am using SideMenu from Jon Kent and it’s pretty easy to do it with the UISideMenuNavigationControllerDelegate
var isInputAccessoryViewEnabled = true {
didSet {
self.inputAccessoryView?.isHidden = !self.isInputAccessoryViewEnabled
if self.isInputAccessoryViewEnabled {self.becomeFirstResponder()}
}
}
func sideMenuWillAppear(menu: UISideMenuNavigationController, animated: Bool) {
let inputWindow = UIApplication.shared.windows.filter({$0.className == "UITextEffectsWindow"}).first
self.inputAccessoryViewSnapShot = inputWindow?.snapshotView(afterScreenUpdates: false)
if let snapShotView = self.inputAccessoryViewSnapShot, let navView = self.navigationController?.view {
navView.addSubview(snapShotView)
}
self.isInputAccessoryViewEnabled = false
}
func sideMenuDidDisappear(menu: UISideMenuNavigationController, animated: Bool) {
self.inputAccessoryViewSnapShot?.removeFromSuperview()
self.isInputAccessoryViewEnabled = true
}
I hope that helps :)

UINavigationBar in UINavigationController rotations not acting as expected (items shifted up)

I've been working on a set of iOS action extensions I seem to be having a problem with the way that the UINavigationBar is being displayed in the app if it is controlled by a UINavigationController.
The problem, in short is that the navigation bar gets shifted up when you rotate from portrait, to landscape, and back to portrait again. The problem is exacerbated if you rotate all the way around. I wish I could provide images, but I cannot given the rules of StackOverflow.
Regardless, this was tested using a only slightly modified Action App Extension (modified to lower target OS, silence a warning about the NSExtensionActiviationRule, etc), both with a UINavigationBar embedded in a UINavigationController and not embedded, just as a view in the ActionViewController. When not in an UINavigationController, the rotations look as expected, but retains a white patch around the status bar (and the navigation bar is slightly wider in the landscape orientation), but when put into a UINavigationController, the bar button items are shifted up.
This was tested pretty thoroughly in 8.2, and even now running the slightly modified app, it looks as one would expect. Does anyone know exactly going on here? Why does the button get shifted up when I use a UINavigationController in 8.3 and not 8.2? I can't seem to find any documentation on a change in 8.3 that would affect it, and no documentation that UINavigationController shouldn't be used in an Action Extension, etc. Any help would be greatly appreciated.
Here is the code, if you are interested, for the ViewController. Its not particularly enlightening, as it's basically what Apple gives you:
import UIKit
import MobileCoreServices
class ActionViewController: UIViewController {
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the item[s] we're handling from the extension context.
// For example, look for an image and place it into an image view.
// Replace this with something appropriate for the type[s] your extension supports.
var imageFound = false
for item: AnyObject in self.extensionContext!.inputItems {
let inputItem = item as! NSExtensionItem
for provider: AnyObject in inputItem.attachments! {
let itemProvider = provider as! NSItemProvider
if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
// This is an image. We'll load it, then place it in our image view.
weak var weakImageView = self.imageView
itemProvider.loadItemForTypeIdentifier(kUTTypeImage as String, options: nil, completionHandler: { (url, error) in
let imageURL = url as! NSURL?;
if imageURL != nil {
NSOperationQueue.mainQueue().addOperationWithBlock {
if let imageView = weakImageView {
let image = UIImage(contentsOfFile: imageURL!.path!);
imageView.image = image;
}
}
}
})
imageFound = true
break
}
}
if (imageFound) {
// We only handle one image, so stop looking for more.
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func done() {
// Return any edited content to the host app.
// This template doesn't do anything, so we just echo the passed in items.
self.extensionContext!.completeRequestReturningItems(self.extensionContext!.inputItems, completionHandler: nil)
}
}
Thanks in advance!

Resources