Cannot create an Envelope using online signing mode - ios

I'm trying to create an Envelop and sign it using online mode.
At first, I logged in to my account
#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)
}
After that, I created my test Envelop:
final class EnvelopeManager {
private let envelopesManager = DSMEnvelopesManager()
private let templateManager = DSMTemplatesManager()
// MARK: - Lifecycle
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let documentURL = R.file.tgkCapitalPortfolioBAgreementPdf(),
let documentData = try? Data(contentsOf: documentURL) else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let document = DSMDocument()
document.name = R.file.tgkCapitalPortfolioBAgreementPdf.name
document.documentId = "1"
document.documentBase64 = documentData.base64EncodedString()
envelopDefinition.documents = [document]
let signHere = DSMSignHere()
signHere.documentId = document.documentId
signHere.pageNumber = 1
signHere.recipientId = "1"
signHere.anchorXOffset = 100
signHere.anchorYOffset = 100
signHere.tabId = "1"
let tabs = DSMTabs()
tabs.signHereTabs = [signHere]
let signer = DSMSigner()
signer.canSignOffline = false
signer.email = config.email
signer.name = config.userName
signer.recipientId = "1"
signer.tabs = tabs
let signers: [DSMSigner] = [signer]
let recipients = DSMRecipients()
recipients.signers = signers
envelopDefinition.recipients = recipients
envelopDefinition.status = "sent"
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .online) { [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.presentSigning(withPresenting: presenter,
envelopeId: envelopeID,
animated: true) { (viewController, error) in
if let viewController = viewController {
print(viewController)
}
if let error = error {
print(error.localizedDescription)
}
}
}
}
But here
envelopesManager.composeEnvelope(with: envelopDefinition, signingMode: .online) { [weak self] envelopID, error in
if let envelopID = envelopID {
print(envelopID)
self?.presentSigning(presenter: presentingController,
envelopeID: envelopID)
} else {
print(error.localizedDescription)
}
}
I have an error:
Envelope creation online is not supported at this moment. Please try
offline mode
When I switched to offline mode I can't use the method
envelopesManager.presentSigning(withPresenting:, enveloped: , animated:, completeion)
Because it works only for envelops which were created in the online mode. And in my case completion block is not executed.
How to resolve this issue? How I can create an envelope in online mode and sign it? What I'm doing wrong? I just want to select my pdf which contains in the project bundle and in some position add the sign.
The sample project which was provided here doesn't match my requirements. Because there is using a template from server and creating envelop via presentComposeEnvelopeController method for choosing document and etc.
XCode 12.4, iOS 13/14, DocuSign 2.4.1 included in the project via CocoaPods.
Edit1
I have updated my perform method:
func perform(with config: DSMAccountInfo, presentingController: UIViewController) {
guard let documentURL = R.file.tgkCapitalPortfolioBAgreementPdf(),
let documentData = try? Data(contentsOf: documentURL) else { return }
let envelopDefinition = DSMEnvelopeDefinition()
envelopDefinition.envelopeName = "Some name"
envelopDefinition.emailSubject = "Please Sign Envelope on Document"
envelopDefinition.emailBlurb = "Hello, Please sign my Envelope"
let document = DSMDocument()
document.name = R.file.tgkCapitalPortfolioBAgreementPdf.name
document.documentId = "1"
document.documentBase64 = documentData.base64EncodedString()
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.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)
}
}
}
and presentSigning method too:
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)
}
}
}
But now I have got the error in the presentSigning method: Envelope is ready for sync and can not be resumed for signing.
And any screen with my pdf document was not shown. How to resolve it? How I can preview this document and after that add the ability for a user to sign it?
Solution
The working code of the EnvelopManager class:
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)
}
}
}
}
After you will have signed the document do not forget to call the sync method on the top view controller

But now I have got the error in the presentSigning method: Envelope is ready for sync and can not be resumed for signing.
Envelope is ready for sync and can not be resumed for signing. denotes the state that envelope has no local signers awaiting signing. This could happen because of a few reasons.
Taking a look at the adding recipient section of guide, this adds a Remote Signer to local envelope.
// Create an envelope recipient with name and email and assign an id and type with routing order
DSMEnvelopeRecipient *recipient = [[[[[[DSMRecipientBuilder builderForType: DSMRecipientTypeSigner]
addRecipientId: #"1"]
addSignerName: #"Jane Wood"]
addSignerEmail: #"JaneWood#docusign.com"]
addRoutingOrder: 1]
build];
No local signers have been added to capture signatures. If you need to add a local signer they could represent following cases:
SDK authenticated account could be used as a signer (DSMRecipientTypeSigner). In the example: #"JaneWood#docusign.com" is logged in with SDK with routingOrder (or SigningOrder) of 1.
SDK authenticated account could be used as a host to an in-person-signer (DSMRecipientTypeInPersonSigner). The following example would add an in-person-signer for #"JaneWood#docusign.com" as host:
// Create an envelope recipient with name and email and assign an id and type with routing order
DSMEnvelopeRecipient *recipient = [[[[[[[DSMRecipientBuilder builderForType: DSMRecipientTypeInPersonSigner]
addRecipientId: #"1"]
addHostName: #"Jane Wood"]
addHostEmail: #"JaneWood#docusign.com"]
addSignerName: #"IPS - John Local Doe"]
addRoutingOrder: 1]
build];
Only remote signers (non-local SDK account) have been added to envelope: Invoking syncEnvelopes on such envelopes will send documents and email the remote-signers to complete signing.
Note: All of the local signers have already completed signing: You'd reach this case once all local signatures have been captured, and using syncEnvelopes would send envelope to DocuSign servers.

#igdev: Thanks for adding code snippets and details explaining your approach.
How I can create an envelope in online mode and sign it?
As of current SDK version, creating online envelopes with EnvelopeDefinition is not yet supported.
Following method is only supported for remote-envelopes that have been already created (via DocuSign API or Web) and accessible on the DocuSign account under use by Native-SDK.
envelopesManager.presentSigning(withPresenting:, enveloped: , animated:, completion)
I just want to select my pdf which contains in the project bundle and in some position add the sign.
Compose flow based envelope creation would enable you to use the PDF from the bundle, position tabs and present signing in the offline mode. Here is a guide (Compose Envelope) that lists steps with sample code to get the desired result. Following snippet from compose envelope guide shows how such envelope can be presented for signing:
if (envelope) {
// Compose the envelope to automatically cache it
[self.envelopesManager composeEnvelopeWithEnvelopeDefinition: envelope
signingMode: DSMSigningModeOffline
completion: ^(NSString * _Nullable envelopeId, NSError * _Nonnull error) {
// error checks in case envelope compose failed. Also use notifications for caching related events.
if (error) { ... }
// Resume the envelope to start the signing process
[self.envelopesManager resumeSigningEnvelopeWithPresentingController: self
envelopeId: envelopeId
completion: ^(UIViewController * _Nullable presentedController, NSError * _Nullable error) {
// error checks in case UI presentation failed. Use notifications for other events.
if (error) { ... }
}];
}
];
}
The sample code prior to update (thanks for adding it) to use the tab-frame instead of anchors is correct step as anchors aren't supported in offline mode by the SDK. In order to avoid such issues, it's best to use the EnvelopeBuilder (which in turn uses Recipient and TabBuilder) to create tabs for individual recipients and assign correct frames (e.g. signHere.frame = CGRect(x: 100, y: 100, width: 50, height: 40)). Using EnvelopeBuilder also ensures that custom envelope data goes through validation process.
/*!
* #discussion add frame of the tab (e.g. CGFrameMake(100, 100, 50, 40) for Sign Tab) and return builder.
* #param frame a frame to draw tab within
*/
- (DSMTabBuilder *)addFrame:(CGRect)frame;
Check answer below for envelope ready for sync state:

Related

Requiring consent before proceeding with survey in ResearchKit

I'm currently creating a program that wants to utilize ResearchKit. Before getting into survey questions, I will need to require consent. I am using the Ray Wenderlich (https://www.raywenderlich.com/1820-researchkit-tutorial-with-swift-getting-started) tutorial and have this code set up. I've already tried to go on with the survey part of my program, and I can access it without even going through consent.
import ResearchKit
public var ConsentTask: ORKOrderedTask {
var ctr = 0
let Document = ORKConsentDocument()
Document.title = "Consent"
//section types
let sectionTypes: [ORKConsentSectionType] = [
.overview,
.dataGathering,
.privacy
]
let consentSections: [ORKConsentSection] = sectionTypes.map { contentSectionType in
let consentSection = ORKConsentSection(type: contentSectionType)
if ctr < sectionTypes.count {
if ctr == 0 {
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 1 { //Data Gathering
consentSection.summary = "Summary"
consentSection.content = "Content"
}
else if ctr == 2 { //Privacy
consentSection.summary = "Summary"
consentSection.content = "Content"
}
ctr = ctr + 1
}
return consentSection
}
Document.sections = consentSections
Document.addSignature(ORKConsentSignature(forPersonWithTitle: nil, dateFormatString: nil, identifier: "UserSignature"))
var steps = [ORKStep]()
let visualConsentStep = ORKVisualConsentStep(identifier: "VisualConsent", document: Document)
steps += [visualConsentStep]
let signature = Document.signatures!.first! as ORKConsentSignature
let reviewConsentStep = ORKConsentReviewStep(identifier: "Review", signature: signature, in: Document)
reviewConsentStep.text = "Review the consent"
reviewConsentStep.reasonForConsent = "I agree"
steps += [reviewConsentStep]
//Completion
let completionStep = ORKCompletionStep(identifier: "CompletionStep")
completionStep.title = "Welcome"
completionStep.text = "Thank you for helping!"
steps += [completionStep]
return ORKOrderedTask(identifier: "ConsentTask", steps: steps)
}
And in my view controller, I have
#IBAction func consentTask(_ sender: Any) {
if consentDone == false {
let taskViewController = ORKTaskViewController(task: ConsentTask, taskRun: nil)
taskViewController.delegate = self
present(taskViewController, animated: true, completion: nil)
}
else if consentDone == true {
//would they like to leave the study
}
}
With my efforts of putting a consentDone flag here.
func taskViewController(_ taskViewController: ORKTaskViewController, didFinishWith reason: ORKTaskViewControllerFinishReason, error: Error?) {
taskViewController.dismiss(animated: true, completion: {() in self.consentDone = true})
print(consentDone)
}
However, what happens is that if the user presses cancel on the top right or done at the very end of the consent, it will trigger this always. Is there a way to be able to make sure that a block of code is executed only after everything is finished? Ideally, after this, I would like to make this a flag that the user has already finished the consent. I will redirect the user to a different page every time afterwards until the user leaves the study.
After some trial and error, I found the answer here: https://github.com/ResearchKit/ResearchKit/issues/919
By knowing that the signature of the user means that the user has finished the form, we can do
if result.identifier == "UserSignature"{
print(result)
let consentDoneAnswerResult = result as! ORKConsentSignatureResult
let consentDone = consentDoneAnswerResult.consented
print(consentDone)
}
And this gives consentDone as true when the form is done, not canceled.

Add Shipping charge in Paypal checkout using Braintree SDK

I am implement the Braintree SDK for payment in my E-Commerce application(iOS and Android).but I am not able to and shipping charge in “BTPayPalRequest”.
How to add Shipping charge in this SDK?
My Code is below:
braintreeClient = BTAPIClient(authorization: “Key”)!
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: strAmmount)
/// Cart Item
var items = [BTPayPalLineItem]()
for Dict in ArrMyCartDetails{
let item = BTPayPalLineItem(quantity: String(Q), unitAmount: String(SP), name: strProductName, kind: .debit)
items.append(item)
}
request.lineItems = items
request.intent = .sale
request.displayName = AppTitle
request.isShippingAddressRequired = false
request.isShippingAddressEditable = false
request.shippingAddressOverride = BTPostalAddress.init()
request.currencyCode = "AUD"
payPalDriver.requestOneTimePayment(request) { (tokenizedPayPalAccount, error) in
if let tokenizedPayPalAccount = tokenizedPayPalAccount {
print("Got a nonce: \(tokenizedPayPalAccount.nonce)")
} else if error != nil {
// Handle error here...
self.ShowMessage(strMsg: "something wrong in payment process.")
} else {
// Buyer canceled payment approval
self.ShowMessage(strMsg: "you cancel payment!")
}
}

Video Player not loading upon return from modal

I am working on an iOS app with XCode 9.3 and Swift 4.
I have a setup where I have a screen, Episode view, with information on a particular video.
For a user that is not logged in, The screen contains:
an image at the top
underneath the image information about the video and a small description.
a Sign in button
The Sign In button opens a modal. Whenever a
user taps on this button, the user should be signed in and then return to the Episode view where the image should be replaced by a video
player (we use JW) with the respective episode.
I created a delegate that would call a method in the EpisodeViewController upon signing in:
func refreshTopView() {
subscribed = 1
setUpTopView()
print("View has refreshed")
}
Upon logging in, the delegate calls setUpTopView()
func setUpTopView() {
if subscribed == 1 {
// Hide the image view
episodeImage.isHidden = true
// Set up URL to video
let linktoJSON = "https://content.jwplatform.com/feeds/" + (episode?.interviewKey)! + ".json";
// Set video screen flag to true
videoScreen = true
// Get the feed for the video
getFeed(url: linktoJSON)
} else {
// Hide the container view for the video
containerView.isHidden = true
// Get the show path and image file and set up the url
let showPath = episode?.showPath
let imageFile = episode?.imageFile
let url = "https://examplesite.com/ios/images/" + showPath! + "/slider/" + imageFile! + ".jpg"
// Show the image
episodeImage.sd_setImage(with: URL(string: url))
}
}
which in turn calls getFeed() since the user has been verified as subscribed
func getFeed(url: String) {
Alamofire.SessionManager.default
.requestWithoutCache(url, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
self.buildPlaylistSources(json: json)
case .failure(let error):
print(error)
}
}
}
This will call the function to build the playlist for the JW Player,
func buildPlaylistSources(json: JSON) {
playlistSources = [[String:String]]()
if let playlistItems = json["playlist"].array {
for item in playlistItems {
if let sources = item["sources"].array {
// For each source
var tempSource = [String:String]()
for source in sources {
if let sourceType = source["type"].string {
if sourceType.hasPrefix("video"){
let key = source["label"].stringValue
let value = source["file"].stringValue
tempSource[key] = value
}
}
}
playlistSources.append(tempSource)
}
}
createPlayer()
}
}
and subsequently set up the player and add it to the screen
func createPlayer() {
let config: JWConfig = JWConfig()
let showPath = episode?.showPath
let imageFile = episode?.imageFile
let videoImage = "https://examplesite.com/ios/images/" + showPath! + "/slider/" + imageFile! + ".jpg"
for playlistSource in playlistSources {
let item = JWPlaylistItem()
var sourceArray = [JWSource]()
for (key, value) in playlistSource {
sourceArray.append(JWSource(file: value, label: key))
}
item.sources = sourceArray
item.image = videoImage
playlist.append(item)
}
config.playlist = playlist
config.controls = true
player = JWPlayerController(config: config)
player.delegate = self
let frame: CGRect = containerView.bounds
self.player.view.frame = frame
self.player.view.autoresizingMask = [UIViewAutoresizing.flexibleBottomMargin, UIViewAutoresizing.flexibleHeight, UIViewAutoresizing.flexibleLeftMargin, UIViewAutoresizing.flexibleRightMargin, UIViewAutoresizing.flexibleTopMargin, UIViewAutoresizing.flexibleWidth]
self.player.forceFullScreenOnLandscape = true
self.player.forceLandscapeOnFullScreen = true
containerView.addSubview(player.view)
print("jwplayer")
}
Even though the code is running all the way to the end, and the initial image is hidden, the player is never loaded. If however I then navigate to a different section and then return to the Episode view, the video loads without issues. Also, if the user runs the app while having previously logged in, the videos load fine. It's just upon returning from the modal login screen that the video player won't load.
I checked the output screen and got the following:
2018-04-11 08:52:54.515047-0500 MyAppIOS[8506:1047732] [MediaRemote] [AVOutputContext] WARNING: AVF context unavailable for +[MRAVOutputContext sharedAudioPresentationContext]_block_invoke
2018-04-11 08:52:54.515273-0500 MyAppIOS[8506:1047732] [MediaRemote] [AVOutputContext] WARNING: AVF context unavailable for +[MRAVOutputContext createOutputContextWithUniqueIdentifier:]
2018-04-11 08:52:57.476625-0500 MyAppIOS[8506:1050590] [0x7f95a686f000] Decoding failed with error code -1
2018-04-11 08:52:57.476904-0500 MyAppIOS[8506:1050590] [0x7f95a686f000] Decoding: C0 0x02800168 0x0000354A 0x11111100 0x00000000 16384
2018-04-11 08:52:57.477102-0500 MyAppIOS[8506:1050590] [0x7f95a686f000] Options: 640x360 [FFFFFFFF,FFFFFFFF] 00024060
As answered by NSGangster in the comments:
Upon a going to the VC the first time (not logged in) you set containerView.isHidden = true. you Probably should set this to false somewhere if you want it to show back up. Which is why I assume it works if you reload the VC.

Parsing a JsonObject in Swift 4 from an URL

It seems for me this is a very simple task, but even after a lot of researching and trying I can't get it working...
So I have for example this URL, for what I understand this is a api to an JSONObject?!
http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent
If I open this link in my browser I get the following result:
{"images":[{"imageid":"1567153","imageurl_lg":"https://cf.geekdo-images.com/images/pic1567153_lg.jpg","name":null,"caption":"White
power
tiles","numrecommend":"6","numcomments":"0","user":{"username":"manosdowns","avatar":"1","avatarfile":"avatar_id33829.jpg"},"imageurl":"https://cf.geekdo-images.com/6fCr14v025ZKYhXRMnbhYR16Ta8=/fit-in/200x150/pic1567153.jpg"}],"config":{"sorttypes":[{"type":"hot","name":"Hot"},{"type":"recent","name":"Recent"}],"numitems":402,"endpage":402,"galleries":[{"type":"all","name":"All"},{"type":"game","name":"Game"},{"type":"people","name":"People"},{"type":"creative","name":"Creative"}],"categories":[{"type":"","name":"All"},{"type":"BoxFront","name":"BoxFront"},{"type":"BoxBack","name":"BoxBack"},{"type":"Components","name":"Components"},{"type":"Customized","name":"Customized"},{"type":"Play","name":"Play"},{"type":"Miscellaneous","name":"Miscellaneous"},{"type":"Mature","name":"Mature"},{"type":"uncat","name":"Uncategorized"}],"licensefilters":[{"type":"","name":"Any"},{"type":"reuse","name":"Copying
allowed"},{"type":"commercial","name":"Commercial use
allowed"},{"type":"modify","name":"Modification
allowed"}],"datefilters":[{"value":"alltime","name":"All
Time"},{"value":"today","name":"Today"},{"value":"twodays","name":"Two
Days"},{"value":"last7","name":"Last 7
Days"},{"value":"last30","name":"Last 30
Days"},{"value":"year","name":"Last 365
Days"}],"filters":[{"name":"Licenses","listname":"licensefilters","type":"licensefilter"},{"name":"Category","listname":"categories","type":"tag"},{"name":"Gallery","listname":"galleries","type":"gallery"}]}}
Now my first attempt was to parse this link the way I parse homepages:
guard let myURL = URL(string: link) else { > print("Error: \(link) doesn't seem to be a valid URL")
return
}
do {
link = try String(contentsOf: myURL, encoding: .ascii)
} catch let error {
print("Error: \(error)")
}
But this doesn't work as I now understand that's because this is JSON coded?!
I searched for parsing JSON and found some explanations for encoding and decoding, but my problem is that in all examples given, the explanations start by "having" already the contents of the JsonObject.
My problem is that I can read the contents of the URL in the browser but I would need the content of the URL in Xcode itself, so I can parse it?!
So in my specific case I would only need the content of "imageurl_lg"
...I would know how to do it if I could display the content I can see in my browser also in Xcode - but how do I get the contents of the link into Xcode?
For reference, I also read following instructions, but couldn't apply them to my example...
https://www.raywenderlich.com/172145/encoding-decoding-and-serialization-in-swift-4
https://grokswift.com/json-swift-4/
and some more but they didn't help me...
You need to use a URLSession task to do this, and after that you need to use JSONSerialization in this example I return a dictionary of [String:Any] you can convert it to whatever Model you need
Use this Code
func fetchData(completion: #escaping ([String:Any]?, Error?) -> Void) {
let url = URL(string: "http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:Any]{
completion(array, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
How use it
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
fetchData { (dict, error) in
debugPrint(dict)
}
}
Result Log printed
Optional(["config": {
categories = (
{
name = All;
type = "";
},
{
name = BoxFront;
type = BoxFront;
},
{
name = BoxBack;
type = BoxBack;
},
{
name = Components;
type = Components;
},
{
name = Customized;
type = Customized;
},
{
name = Play;
type = Play;
},
{
name = Miscellaneous;
type = Miscellaneous;
},
{
name = Mature;
type = Mature;
},
{
name = Uncategorized;
type = uncat;
}
);
datefilters = (
{
name = "All Time";
value = alltime;
},
{
name = Today;
value = today;
},
{
name = "Two Days";
value = twodays;
},
{
name = "Last 7 Days";
value = last7;
},
{
name = "Last 30 Days";
value = last30;
},
{
name = "Last 365 Days";
value = year;
}
);
endpage = 402;
filters = (
{
listname = licensefilters;
name = Licenses;
type = licensefilter;
},
{
listname = categories;
name = Category;
type = tag;
},
{
listname = galleries;
name = Gallery;
type = gallery;
}
);
galleries = (
{
name = All;
type = all;
},
{
name = Game;
type = game;
},
{
name = People;
type = people;
},
{
name = Creative;
type = creative;
}
);
licensefilters = (
{
name = Any;
type = "";
},
{
name = "Copying allowed";
type = reuse;
},
{
name = "Commercial use allowed";
type = commercial;
},
{
name = "Modification allowed";
type = modify;
}
);
numitems = 402;
sorttypes = (
{
name = Hot;
type = hot;
},
{
name = Recent;
type = recent;
}
); }, "images": <__NSSingleObjectArrayI 0x600000010710>( {
caption = "White power tiles";
imageid = 1567153;
imageurl = "https://cf.geekdo-images.com/6fCr14v025ZKYhXRMnbhYR16Ta8=/fit-in/200x150/pic1567153.jpg";
"imageurl_lg" = "https://cf.geekdo-images.com/images/pic1567153_lg.jpg";
name = "<null>";
numcomments = 0;
numrecommend = 6;
user = {
avatar = 1;
avatarfile = "avatar_id33829.jpg";
username = manosdowns;
}; } ) ])
Updated fixing "App Transport Security has blocked a cleartext" error
Adjust your info.plist
So this is what playground gave me.
import UIKit
var str = "Hello, playground"
func makeGetCall() {
guard let myURL = URL(string: "http://api.geekdo.com/api/images?ajax=1&gallery=all&nosession=1&objectid=127023&objecttype=thing&pageid=357&showcount=1&size=thumb&sort=recent") else {
print("Error: \(link) doesn't seem to be a valid URL")
return
}
do {
var content = try String(contentsOf: myURL, encoding: .ascii)
print("Content: \(content)")
} catch let error {
print("Error: \(error)")
}
}
makeGetCall()
Prints

Retrieve randomly generated child ID from Firebase

I have Firebase generating random keys for my records in a Swift app. How do I go about retrieving that key if I want to change a specific record? In my example below, I'm marking a task complete but when I hit the button it doesn't work as intended because I am not referencing the particular task but to reference the task I need the randomly generated key.
func doneHit(cell:TaskCell) {
if let ip = tableView.indexPathForCell(cell) {
var task = tasksInSectionArray[ip.section][ip.row]
let tasksRef = ref.childByAppendingPath("tasks")
ref.observeSingleEventOfType(.Value, withBlock: { snapshot in
let doneRef = tasksRef.childByAppendingPath("\(snapshot.key)/done")
if task.done == false {
task.done = true
cell.checkBox.image = UIImage(named: "checkedbox")
doneRef.setValue(task.done)
}
else {
task.done = false
cell.checkBox.image = UIImage(named: "uncheckedbox")
doneRef.setValue(task.done)
}
let completedByRef = tasksRef.childByAppendingPath("\(snapshot.key)/completedBy")
if task.done == true {
completedByRef.setValue(self.user)
cell.detailLabel.text = "Completed By: \(self.user)"
}
else {
completedByRef.setValue("")
cell.detailLabel.text = ""
}
})
}
}
My Firebase structure:
tasks
randomly generated ID
title:
description:
randomly generated ID
title:
description:
Update 1:
I have updated my code to get the IDs for all of the tasks but the functionality of updating the backend isn't working properly. It is only letting update the tasks created in the app. There are 150 tasks that I imported using the web Dashboard. Those are the ones I can't get to update.
func doneHit(cell:TaskCell) {
if let ip = tableView.indexPathForCell(cell) {
var task = tasksInSectionArray[ip.section][ip.row]
let tasksRef = ref.childByAppendingPath("tasks")
var taskID = ""
tasksRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
for task in snapshot.children {
let _tasks = task as! FDataSnapshot
let id = _tasks.key
print(id)
taskID = id
}
let doneRef = tasksRef.childByAppendingPath("\(taskID)/done")
if task.done == false {
task.done = true
cell.checkBox.image = UIImage(named: "checkedbox")
doneRef.setValue(task.done)
}
else {
task.done = false
cell.checkBox.image = UIImage(named: "uncheckedbox")
doneRef.setValue(task.done)
}
let completedByRef = tasksRef.childByAppendingPath("\(taskID)/completedBy")
if task.done == true {
completedByRef.setValue(self.user)
cell.detailLabel.text = "Completed By: \(self.user)"
}
else {
completedByRef.setValue("")
cell.detailLabel.text = ""
}
})
}
}
To create a randomly generated key you need to use childByAutoId() (which I can't see in your example code).
This will return a Firebase reference you could use and which will return the key with it's .key property
var post1Ref = ref.childByAutoId()
post1Ref.setValue(post1)
var postId = post1Ref.key
See documentation here
tasksRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
for task in snapshot.children {
guard let taskSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key
// do other things
}
}
Following Tim's answer for Swift 3 and Firebase 4 I had to use the below
for task in snapshot.children {
guard let taskSnapshot = task as? DataSnapshot else {
continue
}
let id = taskSnapshot.key
An alternative to Ron's answer involves generating the key first and then using it to set value:
let autoId = databaseReference.childByAutoId().key
databaseReference.child(autoId).setValue(YOUR_VALUE)

Resources