How to print SFSafariViewController full web page? - ios

I am opening a pdf file in SFSafariViewController. Now I want to print this PDF from SFSafariViewController.
But Share button of safari view controller does not have print option.
Please tell me how to add print functionality to SFSafariViewController.

You can custom activity.
extension ViewController: SFSafariViewControllerDelegate {
func safariViewController(_ controller: SFSafariViewController, activityItemsFor URL: URL, title: String?) -> [UIActivity] {
let acitivity = CustomActivity()
return [acitivity]
}
func printURL(url: URL) {
}
}
CustomActivity.swift
import Foundation
import UIKit
class CustomActivity: UIActivity {
override class var activityCategory: UIActivityCategory {
return .action
}
override var activityType: UIActivityType? {
return UIActivityType.print
}
override var activityTitle: String? {
return "Print"
}
override var activityImage: UIImage? {
return nil
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
return true
}
override func perform() {
// do something here
}
}

Related

iOS14 CNContactViewController not showing delete button issue

When I press Edit from contact card, my CNContactViewController is not showing the delete option in the bottom of the screen.
NB: the button remains shown for iOS 13.
import Foundation
import ContactsUI
import SwiftUI
struct CNContactViewControllerRepresentable: UIViewControllerRepresentable {
typealias UIViewControllerType = CNContactViewController
var contact: Binding<CNContact>
var presentingEditContact: Binding<Bool>
func makeCoordinator() -> CNContactViewControllerRepresentable.Coordinator {
Coordinator(self)
}
func makeUIViewController(context: UIViewControllerRepresentableContext<CNContactViewControllerRepresentable>) -> CNContactViewControllerRepresentable.UIViewControllerType {
let controller = CNContactViewController(forNewContact: contact.wrappedValue)
controller.delegate = context.coordinator
return controller
}
func updateUIViewController(_ uiViewController: CNContactViewControllerRepresentable.UIViewControllerType, context: UIViewControllerRepresentableContext<CNContactViewControllerRepresentable>) {
//
}
// Nested coordinator class, the prefered way stated in SwiftUI documentation.
class Coordinator: NSObject, CNContactViewControllerDelegate {
var parent: CNContactViewControllerRepresentable
init(_ contactDetail: CNContactViewControllerRepresentable) {
self.parent = contactDetail
}
func contactViewController(_ viewController: CNContactViewController, didCompleteWith contact: CNContact?) {
parent.contact.wrappedValue = contact ?? parent.contact.wrappedValue
parent.presentingEditContact.wrappedValue = false
}
func contactViewController(_ viewController: CNContactViewController, shouldPerformDefaultActionFor property: CNContactProperty) -> Bool {
return true
}
}
}
.sheet(isPresented: $viewModel.presentingEditContact) {
NavigationView {
if #available(iOS 14, *) {
return AnyView(CNContactViewControllerRepresentable(contact: self.$viewModel.contact, presentingEditContact: $viewModel.presentingEditContact)
.navigationBarTitle("Edit Contact")
.edgesIgnoringSafeArea(.top))
} else {
return AnyView(CNContactViewControllerRepresentable(contact: self.$viewModel.contact, presentingEditContact: $viewModel.presentingEditContact)
.edgesIgnoringSafeArea(.top))
}
}
}

Custom UIActivityViewController not appear image when it is in the "More" menu item

I created custom UIActivity and override variable activityImage used my image.
fileprivate extension UIActivity.ActivityType {
static let extendedMessage =
UIActivity.ActivityType("ExtendedMessage")
}
Custom UIActivity:
fileprivate class ExtendedMessageActivity: UIActivity {
private let phoneNumbers: [String]
private var message: String?
init(phoneNumbers: [String]) {
self.phoneNumbers = phoneNumbers
super.init()
}
override static var activityCategory: UIActivity.Category {
return .share
}
override var activityType: UIActivity.ActivityType? { return .extendedMessage }
override var activityTitle: String? { return NSLocalizedString("Message", comment: "") }
override var activityImage: UIImage? {
return UIImage(named: "message-app-icon")
}
override func canPerform(withActivityItems activityItems: [Any]) -> Bool {
....
}
override func prepare(withActivityItems activityItems: [Any]) {
....
}
override func perform() {
....
}
}
The image appears in the set.
I have a problem the image does not appear when it is in the "More" menu item.
Why the image does not appear?
Had tried to find documentation for the activity settings icon. Not found proper documentation. Just add the below code in your custom UIActivity, Working fine for me with Swift 5.2
#objc var _activitySettingsImage: UIImage? {
return UIImage(named: "activitySettingsIcon")
}
Don't forgot to activitySettingsIcon image set in 29pt.
I solved the same issue by adding the following (in Objective C, but you get the idea).
- (UIImage *)_activitySettingsImage {
return [UIImage imageNamed:#"message-app-icon-for-setting"];
}
Apparently, image for more section should be smaller than the one in _activityImage. At the time of writing (XCode 12.3), 60pt for activityImage and 30pt for activitySettingsImage work for me. (I also added #2x for each image)..

Accessing UI operations from another class

I have a "Share on Facebook" button in my app at 3 different view controllers. So I wrote a class which name is "ShareCentral" and i want to do all sharing operations in this class. But for showing share dialog i need to pass uiviewcontroller to my ShareCentral class. I did like that:
class ShareCentral {
var UIVC: UIViewController
init(vc:UIViewController) {
self.UIVC = vc
}
func shareOnFacebook() {
var content = LinkShareContent(url: URL(string:userProfileLink)!)
do {
try ShareDialog.show(from: UIVC, content: content)
}catch (let error) {
print(error)
}
}
}
And this is my view controller:
class SettingsViewController: UIViewController {
let shareCentral = ShareCentral(vc: self)
#IBAction func shareButtonClicked(_ sender: AnyObject) {
self.shareCentral.shareOnFacebook()
}
}
I am getting following compiler error:
SettingsViewController.swift:40:41: Cannot convert value of type '(SettingsViewController) -> () -> (SettingsViewController)' to expected argument type 'UIViewController'
I know if i change the type of UIVC to "SettingsViewController" the problem will disappear. But as i said before i am gonna use this method in three different view controllers.
How can i resolve this problem?
Try this instead :
class ShareCentral {
unowned var UIVC: UIViewController
init(vc:UIViewController) {
self.UIVC = vc
}
func shareOnFacebook() {
var content = LinkShareContent(url: URL(string:userProfileLink)!)
do {
try ShareDialog.show(from: UIVC, content: content)
} catch (let error) {
print(error)
}
}
}
class SettingsViewController: UIViewController {
var shareVC: ShareVC!
override func viewDidLoad() {
super.viewDidLoad()
self.shareVC = ShareVC(vc: self)
}
#IBAction func shareButtonClicked(_ sender: AnyObject) {
self.shareCentral.shareOnFacebook()
}
}

Swift closure in protocol extension

I want to Decorate UIViewController with the ability to adjust it's interface when setInteractionEnabled method is called from another class (ex. Network State Manager). All changes (if any) should be provided in the concrete controller by overriding onInteractionChanged. Here is my code:
import Foundation
typealias InteractionClosure = ((enabled: Bool) -> Void)
protocol Interaction: class {
var onInteractionChanged: InteractionClosure? { get set }
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
// Default: Do nothing
// Throws: - Extensions may not contain stored properties
var onInteractionChanged: InteractionClosure? = nil
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
}
extension UIViewController : Interaction {}
How to add default implementation for onInteractionChanged?
Answering my own question is something usually I don't do, but here is my solution:
typealias InteractionClosure = (enabled: Bool) -> Void
protocol Interaction: class {
func addOnInteractionChanged(closure: InteractionClosure)
func setInteractionEnabled(enabled: Bool)
}
extension Interaction where Self: UIViewController {
func addOnInteractionChanged(closure: InteractionClosure) {
onInteractionChanged = closure
}
func setInteractionEnabled(enabled: Bool) {
onInteractionChanged?(enabled: enabled)
}
// MARK: - Private
private var onInteractionChanged: InteractionClosure? {
get {
let wrapper =
objc_getAssociatedObject(self, &icAssociationKey) as? ClosureWrapper
return wrapper?.closure
}
set(newValue) {
objc_setAssociatedObject(self,
&icAssociationKey,
ClosureWrapper(newValue),
.OBJC_ASSOCIATION_RETAIN)
}
}
}
extension UIViewController : Interaction {}
// Helpers
private var icAssociationKey: UInt8 = 0
private class ClosureWrapper {
var closure: InteractionClosure?
init(_ closure: InteractionClosure?) {
self.closure = closure
}
}
Client class:
class LoginViewController: UIViewController {
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
}
// MARK: - Private
private func setup() {
// ...
addOnInteractionChanged { [unowned self] (enabled) in
self.signInButton.enabled = enabled
self.activityIndicatorView.hidden = !enabled
}
}
}
In manager class:
visibleViewController?.setInteractionEnabled(true)
If you would like property to have only { get } ability, you can use:
protocol TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? { get }
}
extension TestProtocol {
var testClosure: ((_ parameter: Bool) -> Void)? {
return { parameter in
print(parameter)
}
}
}

How to create protocol-oriented generic services?

I'm trying to create a protocol that will serve data for view controllers. I'm trying to take the protocol approach and makes things flexible, so the view controllers can conform using any type of data.
However, I'm getting the error: Protocol 'Serviceable' can only be used as a generic contraint because it has Self or associated type requirements
This is what I'm trying to do:
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([Postable] -> Void)? = nil) {
print("Do something...")
}
}
struct AuthorService: Serviceable {
func get(handler: ([Authorable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Postable {
var title: String { get set }
var content: String { get set }
}
protocol ServiceControllable: class {
var service: Serviceable { get } // Error: Protocol 'Serviceable' can only be used as a generic contraint because it has Self or associated type requirements
}
extension ServiceControllable {
func setupDataSource() {
service.get { items in
// Do something
}
}
}
class MyViewController: ServiceControllable {
let service: Serviceable = PostService() // Error: Same as above
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
}
}
How do I set this up so that my view controllers can implement ServiceControllable and have access to a generic setupDataSource that populates tables, collections, etc?
You want something like this.
import UIKit
protocol Serviceable {
associatedtype DataType
func get(handler: ([DataType] -> Void)?)
}
struct PostService: Serviceable {
func get(handler: ([Postable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Authorable {}
struct AuthorService: Serviceable {
func get(handler: ([Authorable] -> Void)? = nil) {
print("Do something...")
}
}
protocol Postable {
var title: String { get set }
var content: String { get set }
}
protocol ServiceControllable: class {
// THIS is the way to use generic-constraint-protocols in protocols.
associatedtype _Serviceable: Serviceable
var service: _Serviceable { get }
}
extension ServiceControllable {
func setupDataSource() {
service.get { items in
// Do something
}
}
}
class MyViewController: UIViewController, ServiceControllable {
let service = PostService()
override func viewDidLoad() {
super.viewDidLoad()
setupDataSource()
}
}
The related documentation section: Protocol Associated Type Declaratio.

Resources