My app totally freeze after successful login to DocuSign
Here my code:
#IBAction private func signDocument(_ sender: UIButton) {
guard let hostURL = URL(string: Environment.current.docuSignHost) else { return }
isLoading = true
DSMManager.login(withEmail: Environment.current.docuSignUserID,
password: Environment.current.docuSignPass,
integratorKey: Environment.current.docuSignIntegratorKey,
host: hostURL) { [weak self] info, error in
self?.isLoading = false
if let error = error {
self?.error = error
} else {
self?.showDocuSign(info: info)
}
}
}
// MARK: - Helpers
private func showDocuSign(info: DSMAccountInfo?) {
guard let info = info else { return }
envelopManager.perform(with: info, presentingController: self)
}
final class EnvelopeManager {
private let envelopesManager = DSMEnvelopesManager()
private let templateManager = DSMTemplatesManager()
// MARK: - Lifecycle
func sync() {
envelopesManager.syncEnvelopes()
}
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let path = Bundle.main.path(forResource: R.file.tgkCapitalPortfolioBAgreementPdf.name, ofType: "pdf") else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let builder = DSMDocumentBuilder()
builder.addDocumentId("1")
builder.addName(R.file.tgkCapitalPortfolioBAgreementPdf.name)
builder.addFilePath(Bundle.main.path(forResource: R.file.tgkCapitalPortfolioBAgreementPdf.name,
ofType: "pdf")!)
let document = builder.build()
envelopDefinition.documents = [document]
let signHere = DSMSignHere()
signHere.documentId = document.documentId
signHere.pageNumber = 1
signHere.recipientId = "1"
signHere.frame = .init(originX: 100,
originY: 100,
width: 100,
height: 100,
originYOffsetApplied: 50)
signHere.tabId = "1"
let tabs = DSMTabs()
tabs.signHereTabs = [signHere]
let signer = DSMSigner()
signer.email = config.email
signer.name = config.userName
signer.userId = config.userId
signer.clientUserId = config.userId
signer.routingOrder = 1
signer.recipientId = "1"
signer.tabs = tabs
let signers: [DSMSigner] = [signer]
let recipients = DSMRecipients()
recipients.signers = signers
envelopDefinition.recipients = recipients
envelopDefinition.status = "created"
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .offline) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
}
private func presentSigning(presenter: UIViewController, envelopeID: String) {
envelopesManager.resumeSigningEnvelope(withPresenting: presenter,
envelopeId: envelopeID) { (viewController, error) in
if let viewController = viewController {
print(viewController)
}
if let error = error {
print(error.localizedDescription)
}
}
}
}
All closures have a success status but the app freezes and any DocuSign view controllers are not showing.
But if in the second attempt I add the calling the logout before login - all works as expected
#IBAction private func signDocument(_ sender: UIButton) {
guard let hostURL = URL(string: Environment.current.docuSignHost) else { return }
isLoading = true
DSMManager.logout()
DSMManager.login(withEmail: Environment.current.docuSignUserID,
password: Environment.current.docuSignPass,
integratorKey: Environment.current.docuSignIntegratorKey,
host: hostURL) { [weak self] info, error in
self?.isLoading = false
if let error = error {
self?.error = error
} else {
self?.showDocuSign(info: info)
}
}
}
How to resolve this issue? How to make that my app will not freeze after any successful login?
Edit1
I have figured out that the total app freeze is occurring by the first app install. When I open an app that was already installed and I many times opened it earlier and go to DocuSign flow, call logout and finally call login- all works as expected - I don't have any app freeze. But if I don't call the logout method in this chain then my app freezes.
The problem was consistent in that DSMManager.setup() called earlier than applicationDidLaunch. After I moved it to applicationDidLaunch the problem was resolved
Thanks for posting the solution #igdev.
Yes, correct sequence to perform SDK setup is during applicationDidLaunch or afterwards, say during viewDidLoad() of any of the viewControllers (lazy sdk setup & initialization). As long as setup() is done prior to any of the sdk-calls such as login(withAccessToken:...) or cacheEnvelope(...). DocuSign SDK setup() establishes the core data and other essential components required for its functionality.
Typical sequence of calls made with DocuSign SDK.
Application Launch
Initialize DocuSign SDK with DocuSignSDK setup()
Login with DocuSignSDK login(with...). Login could be skipped if a prior SDK session is already established, for e.g. when getting signatures in offline-envelopes using downloaded templates.
Accessing TemplatesManager or EnvelopesManager functionality [Download templates, Sign Envelopes, Sync offline envelopes, etc] and other tasks such as handling DocuSign-SDK notifications to track events (e.g. Signing Completed Envelope Cached, etc).
Logout, it's not required, if needed can perform DSMManager.logout() when client user is logged out.
Related
I have a scenario where I need to load data from a JSON object, I've created an helper class that does that and it looks like this
protocol JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link])
func helper(_: JSONDumpHelper, completionProcess: Double)
}
struct JSONDumpHelper {
static let pointsOfInterest = OSLog(subsystem: "com.mattrighetti.Ulry", category: .pointsOfInterest)
var delegate: JSONDumpHelperDelegate?
func loadFromFile(
with filemanager: FileManager = .default,
from url: URL,
decoder: JSONDecoder = JSONDecoder(),
context: NSManagedObjectContext = CoreDataStack.shared.managedContext,
dataFetcher: DataFetcher = DataFetcher()
) {
os_signpost(.begin, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
let data = try! Data(contentsOf: url)
let dump = try! decoder.decode(Dump.self, from: data)
var tagsHash: [UUID:Tag] = [:]
if let tagsCodable = dump.tags {
for tagCodable in tagsCodable {
let tag = Tag(context: context)
tag.id = tagCodable.id
tag.name = tagCodable.name
tag.colorHex = tagCodable.colorHex
tagsHash[tag.id] = tag
}
}
var groupHash: [UUID:Group] = [:]
if let groupsCodable = dump.groups {
for groupCodable in groupsCodable {
let group = Group(context: context)
group.id = groupCodable.id
group.name = groupCodable.name
group.colorHex = groupCodable.colorHex
group.iconName = groupCodable.iconName
groupHash[group.id] = group
}
}
var links: [Link] = []
if let linksCodable = dump.links {
let total = linksCodable.count
var completed = 0.0
delegate?.helper(self, completionProcess: 0.0)
for linkCodable in linksCodable {
let link = Link(context: context)
link.id = linkCodable.id
link.url = linkCodable.url
link.createdAt = linkCodable.createdAt
link.updatedAt = linkCodable.updatedAt
link.colorHex = linkCodable.colorHex
link.note = linkCodable.note
link.starred = linkCodable.starred
link.unread = linkCodable.unread
if let uuidGroup = linkCodable.group?.id {
link.group = groupHash[uuidGroup]
}
if let tags = linkCodable.tags {
link.tags = Set<Tag>()
for tagUUID in tags.map({ $0.id }) {
link.tags?.insert(tagsHash[tagUUID]!)
}
}
links.append(link)
completed += 1
delegate?.helper(self, completionProcess: completed / Double(total))
}
}
os_signpost(.end, log: JSONDumpHelper.pointsOfInterest, name: "loadFromFile")
}
}
This could potentially be a very long running task, just imagine an array with 1k records that also need to fetch data from the internet (not shown in implementation, error still exist with posted code) and you can easily end up with 10s in execution time.
What I'm trying to achieve is to show the user an alert that will show him the progress of the import process, by updating the values with the delegate protocols below.
extension BackupViewController: JSONDumpHelperDelegate {
func helper(_: JSONDumpHelper, didFinishFetching: [Link]) {
DispatchQueue.main.async {
self.completionViewController.remove()
}
}
func helper(_: JSONDumpHelper, completionProcess: Double) {
DispatchQueue.main.async {
self.completionViewController.descriptionLabel.text = "Completed \(Int(completionProcess * 100))%"
}
}
}
The import method is fired from a UITableView, immediately after the user choses a file from a DocumentPickerViewController
extension BackupViewController: UIDocumentPickerDelegate {
func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
dismiss(animated: true, completion: nil)
}
func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
self.initImport(for: urls)
}
}
private func initImport(for urls: [URL]) {
if let url = urls.first {
completionViewController.add(self, frame: self.view.bounds)
completionViewController.descriptionLabel.text = "Fetching"
DispatchQueue.global(qos: .userInteractive).async {
self.dumpHelper.loadFromFile(from: url)
}
}
}
The problem I am facing is that when the user initiates the import process, the UI is not updated until the process itself is finished.
If I place breakpoints both at the protocol implementations and at the delegate calls in the helper class I can see that the delegate is not called immediately but they all get fired when the process ended (but the alert controller does not update its values).
Just to place some more info I'm going to replicate an import of N elements from JSON:
User clicks import process
initImport is executed (nothing is shown on UI even if I add the custom vc to view)
JSONDumpHelper.loadFromFile is executed entirely, calling delegate N times, nothing called in the delegate implementation
loadFromFile finishes execution
delegate implementation of helper(_:, completionProcess) is executed N times, UI always shows "Completed 0%"
delegate implementation of helper(_:, didFinishFetching) is executed, controller is removed from view
Can anybody point out what is wrong with this implementation? It seems like the loadFromFile function is not working in a separate Queue and UI is stuck and can't update as expected.
Error Domain=MCOErrorDomain Code=5 "Unable to authenticate with the current session's credentials." UserInfo={NSLocalizedDescription=Unable to authenticate with the current session's credentials.}
I put this code in my project.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
prepareImapSession()
}
var imapsession:MCOIMAPSession = MCOIMAPSession()
var error : Error? = nil
func prepareImapSession()
{
// CONFIGURE THAT DEPENDING OF YOUR NEEDS
imapsession.hostname = "imap.gmail.com" // String
imapsession.username = my email // String
imapsession.password = password // String
imapsession.port = 993 // UInt32 number
imapsession.authType = MCOAuthType.saslLogin
imapsession.connectionType = MCOConnectionType.TLS
DispatchQueue.main.asyncAfter(deadline: .now() + 8) { [self] in
self.useImapWithUIDS()
}
imapsession.connectOperation()
}
func useImapWithUIDS() {
// There is more than one option here, explore depending of your needs
// let kind = MCOIMAPMessagesRequestKind()
// let headers = kind.union(MCOIMAPMessagesRequestKind.headers)
// let request = headers.union(MCOIMAPMessagesRequestKind.flags)
let requestKind: MCOIMAPMessagesRequestKind = [.headers, .flags]
let folder : String = "INBOX"
// HERE ALSO EXPLORE DEPENDING OF YOUR NEEDS, RANGE IT IS THE RANGE OF THE UIDS THAT YOU WANT TO FETCH, I SUGGEST TO YOU TO CHANGE THE // NUMBER ONE IF YOU HAVE A LOWER BOUND TO FETCH EMAIL
let uids : MCOIndexSet = MCOIndexSet(range: MCORangeMake(1, UINT64_MAX))
let fetchOperation = imapsession.fetchMessagesOperation(withFolder: folder, requestKind: requestKind, uids: uids)
fetchOperation?.start
{ [self] (err, msg, vanished) -> Void in
if (err != nil)
{
self.error = err
NSLog((err?.localizedDescription)!)
}
else
{
guard let msgs = msg as? [MCOIMAPMessage]
else
{
print("ERROR GETTING THE MAILS")
return
}
for i in 0..<msgs.count
{
// THE SUBJECT
let subject = msgs[i].header.subject
// THE uid for this email. The uid is unique for one email
let uid = msgs[i].uid
self.useImapFetchContent(uidToFetch: uid)
// The sequenceNumber like the nomber say it is the sequence for the emails in the INBOX from the first one // (sequenceNumber = 1) to the last one , it not represent always the same email. Because if you delete one email then //next one will get the sequence number of that email that was deleted
let sequenceNumber = msgs[i].sequenceNumber
}
}
}
}
// MARK: - EXTRACT THE CONTENT OF ONE EMAIL, IN THIS FUNCTION YOU NEED THE uid, THE UNIQUE NUMBER FOR ONE EMAIL
func useImapFetchContent(uidToFetch uid: UInt32) {
let operation: MCOIMAPFetchContentOperation = imapsession.fetchMessageOperation(withFolder: "INBOX", uid: uid)
operation.start { (Error, data) in
if (Error != nil)
{
NSLog("ERROR")
return
}
let messageParser: MCOMessageParser = MCOMessageParser(data: data)
// IF YOU HAVE ATTACHMENTS USE THIS
let attachments = messageParser.attachments() as? [MCOAttachment]
// THEN YOU NEED THIS PROPERTIE, IN THIS EXAMPLE I TAKE THI FIRST, USE WHAT EVER YOU WANT
let attachData = attachments?.first?.data
// FOR THE MESSAGEPARSER YOU CAN EPLORE MORE THAN ONE OPTION TO OBTAIN THE TEXT
let msgPlainBody = messageParser.plainTextBodyRendering()
}
}
}
I using the mailcore2 framework. I got error description Unable to authenticate with the current session's credentials.
It can be related with 2nd factor authentication on your account. It was described on Apple forum.
I followed this guide on how to setup a SLComposeViewController, and it includes on how to set up configuration items.
I'm pulling from a MongoDB database to get the most recently modified item. Here is my configurationItems():
override func configurationItems() -> [Any]! {
let configurationItems = [editConfigurationItem]
return configurationItems
}
and here is my editConfigurationItem variable:
lazy var editConfigurationItem: SLComposeSheetConfigurationItem = {
func getFirstCategory(completion: #escaping(String) -> ()) {
DispatchQueue.main.async {
let email: String = customKeychainWrapperInstance.string(forKey: "email") ?? ""
let password: String = customKeychainWrapperInstance.string(forKey: "password") ?? ""
app.login(credentials: Credentials.emailPassword(email: email, password: password)) { (maybeUser, error) in
DispatchQueue.main.sync {
guard error == nil else {
print("Login failed: \(error!)");
return
}
guard let _ = maybeUser else {
fatalError("Invalid user object?")
}
print("Login succeeded!");
}
let user = app.currentUser!
let configuration = user.configuration(partitionValue: user.id)
let predata = try! Realm(configuration: configuration)
let unsortedData = predata.objects(Tiktoks.self)
let data = unsortedData.sorted(byKeyPath: "date", ascending: false)
let firstCategory = data[0].category
let firstCategoryId = data[0]._id
print(firstCategory)
print(firstCategoryId)
self.selectedId = firstCategoryId
completion(firstCategory)
}
}
print("done")
}
let item = SLComposeSheetConfigurationItem()!
item.title = "collection"
item.valuePending = true
getFirstCategory() { (firstCategory) in
item.valuePending = false
print("completed")
item.value = firstCategory // Using self as the closure is running in background
}
item.tapHandler = self.editConfigurationItemTapped
return item
}()
(Sorry for the messiness of the code, I'm new to Swift)
So far it works, but the item.value variable doesn't get updated in the UI. It "infinitely loads" until you click on the configuration item. When the configuration item is tapped to go to another view though, the actual variable from the database shows for a split second before showing the next view. When you go back from that other view, the actual variable is there.
It looks like to me that the configuration item isn't updating. I see that there is a reloadConfigurationItems(), but by putting that in my editConfigurationItem will cause a loop I would think (and it also errors out too). The documentation even says:
In particular, you don’t need to call this method after you change a configuration item property, because the SLComposeServiceViewController base class automatically detects and responds to such changes.
but it looks like it's not detecting the changes.
Is there a way to refresh item.value and item.valuePending?
I implemented checkout flow and I am getting below error
Code :
func startCheckout() {
// Example: Initialize BTAPIClient, if you haven't already
braintreeClient = BTAPIClient(authorization: clientToken)!
let payPalDriver = BTPayPalDriver(apiClient: braintreeClient)
payPalDriver.viewControllerPresentingDelegate = self
payPalDriver.appSwitchDelegate = self // Optional
// Specify the transaction amount here. "2.32" is used in this example.
let request = BTPayPalRequest(amount: "2.32")
request.currencyCode = "USD" // Optional; see BTPayPalRequest.h for more options
payPalDriver.requestOneTimePayment(request) { (tokenizedPayPalAccount, error) in
if let tokenizedPayPalAccount = tokenizedPayPalAccount {
print("Got a nonce: \(tokenizedPayPalAccount.nonce)")
// Access additional information
let email = tokenizedPayPalAccount.email
let firstName = tokenizedPayPalAccount.firstName
let lastName = tokenizedPayPalAccount.lastName
let phone = tokenizedPayPalAccount.phone
// See BTPostalAddress.h for details
let billingAddress = tokenizedPayPalAccount.billingAddress
let shippingAddress = tokenizedPayPalAccount.shippingAddress
} else if let error = error {
print("Error here")
// Handle error here...
} else {
print("Buyer cancelled the payment")
// Buyer canceled payment approval
}
}
}
I following this documentation link
Most common reason for this issue is because of configuration in paypal.
You can check your configuration in below link
https://docs.woocommerce.com/document/paypal-standard/
This link is for woo-commerce but will help you in your account configuration.
I've been struggling with this for 2 hours now. I'm building an app in Swift, using Firebase Database and storage.
The goal is to update User profile. The user has 2 images - Profile and header. Now, I have to first check if they've selected an image from the photo library, if not - just get the old URL from the database and submit it back to the database with the rest of the updated information. If it's a new selected image, upload the image to the Storage, get back the URL using downloadURL assign it to the var storageHeaderDownloadedURL and/or var storageProfileDownloadedURL and submit the string values with the rest of the user data to Firebase Database.
The problem is that it obviously assigns the values of an empty String (I've declared them as such) BEFORE I get back the downloaded URL. If the user doesn't update the images but the rest of the UITextFields it all works, the old URL is submitted to the Firebase Database.
My question is how do I execute the downloaded URL methods for from the storage and then assign it to var storageHeaderDownloadedURL and var storageProfileDownloadedURL first hand?
func updateUserProfile ()
{
if let userID = FIRAuth.auth()?.currentUser?.uid
{
// Note: Storage references to profile images & profile headers folder
let storageUserProfileID = Storage.storage.profile_images.child(userID)
let storageUserHeaderID = Storage.storage.profile_headers.child(userID)
guard let imageProfile = profileImage.image else { return }
guard let headerImage = headerImage.image else { return }
if let newProfileImage = UIImagePNGRepresentation(imageProfile), let newHeaderImage = UIImagePNGRepresentation(headerImage)
{
storageUserProfileID.put(newProfileImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserProfileID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: nil)
return
}
else
{
if let profileImgDownloadedURL = url?.absoluteString
{
self.storageProfileDownloadedURL = profileImgDownloadedURL
print(self.storageProfileDownloadedURL)
self.selectedProfileImage = .True
}
}
})
})
storageUserHeaderID.put(newHeaderImage, metadata: nil, completion: { (metadata, error) in
if error != nil
{
showAlert(title: "Oops!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
// Get the URL from the storage
storageUserHeaderID.downloadURL(completion: { (url, error) in
if error != nil
{
showAlert(title: "Oops!!!", msg: (error?.localizedDescription)!, actionButton: "OK", viewController: self)
return
}
else
{
if let headerImgDownloadedURL = url?.absoluteString
{
self.storageHeaderDownloadedURL = headerImgDownloadedURL
print(self.storageHeaderDownloadedURL)
self.selectedHeaderImage = .True
}
}
})
})
//Note: Update the info for that user in Database
print(self.storageHeaderDownloadedURL)
print(self.storageProfileDownloadedURL)
var finalHeaderImageURL = String()
switch self.selectedHeaderImage {
case .True:
finalHeaderImageURL = self.storageHeaderDownloadedURL
break
case .False:
finalHeaderImageURL = self.oldHeaderImageInDB
break
}
print(finalHeaderImageURL)
var finalProfileImageURL = String()
switch self.selectedProfileImage {
case .True:
finalProfileImageURL = self.storageProfileDownloadedURL
break
case .False:
finalProfileImageURL = self.oldProfilePhotoImageInDB
break
}
print(finalProfileImageURL)
guard let newDisplayName = self.displayNameTextField.text else { return }
guard let newLocation = self.locationTextField.text else { return }
guard let newDescription = self.bioTextField.text else { return }
guard let newWebsite = self.websiteTextField.text else { return }
guard let newBirthday = self.birthdayTextField.text else { return }
let newUpdatedUserDictionary = ["imageProfile": finalProfileImageURL,
"imageHeader" : finalHeaderImageURL,
"description" : newDescription,
"location": newLocation,
"displayName": newDisplayName,
"website": newWebsite,
"birthday": newBirthday,
]
Database.dataService.updateUserProfile(uid: userID, user: newUpdatedUserDictionary)
showAlert(title: "Hey", msg: "Your profile was updated", actionButton: "OK", viewController: self)
} // Get new uploaded profile and header image URLs
}
}
The enums I use for the switch statements to determine if it's an old URL or a new one:
enum SelectedHeaderImage
{
case True
case False
}
enum SelectedProfileImage
{
case True
case False
}
Class outlets:
var storageProfileDownloadedURL = String()
var storageHeaderDownloadedURL = String()
var oldProfilePhotoImageInDB = String()
var oldHeaderImageInDB = String()
var selectedHeaderImage = SelectedHeaderImage.False
var selectedProfileImage = SelectedProfileImage.False`
From what I understood, your problem is with queuing. You want the code below to execute after the download is complete but it executes in its normal flow. If this is what your problem is then I would suggest you to create another enum, with three download states/count. And move that code below you want to be executed later in a function. Increment the state of new enum when download is complete. It would look something like this:
enum DownloadCount
{
case Zero
case One
case Two
}
var downloadCount = DownloadCount.Zero
and in each of the success block of your download complete change it to, I will just write one here to give you the idea of what needs to be done.
if let profileImgDownloadedURL = url?.absoluteString
{
self.storageProfileDownloadedURL = profileImgDownloadedURL
print(self.storageProfileDownloadedURL)
self.selectedProfileImage = .True
if(downloadCount == .Zero)
{
downloadCount = DownloadCount.One
}
else
{
downloadCount = DownloadCount.Two
}
self.newAssigningFunction()
}
func newAssigningFunction()
{
if(downloadCount == .Two)
{
//Do your storage/saving work here
}
}
Also if you need to execute this function again, it would be best to set downloadCount back to Zero at start of your updateUserProfile function. Let me know if somethis is unclear or you need further help. Or if this was not your case.