In this guide, we'll show how to embed a form builder with Swift using the Joyfill’s Swift form builder SDK, and learn how to render your first form in minutes.
You can think of Joyfill as a sort of Stripe or Twilio, but for forms.
Reference for integrating with the JoyDoc iOS SDK. Note that it's built using Swift w/ UIKit. So if you are using SwiftUI you will need to wrap the file per Apple Developer Guides suggest (we have included an example as well).
Setup
Please see our Overview and Getting Started Guide
Requirements and Dependencies
View Package README
Installation
Swift Package Manager
To integrate using Swift Package Manager, Swift version >= 5.3 is required.
In your Xcode project from the Project Navigator (Xcode ❯ View ❯ Navigators ❯ Project ⌘ 1) select your project, activate the Package Dependencies tab and click on the plus symbol ➕ to open the Add Package popup window:
Enter the JoyDoc package URL https://github.com/joyfill/components-ios/tree/main into the search bar in the top right corner of the Add Package popup window.
Select components-ios package
Choose your Dependency Rule (we recommend Up to Next Major Version).
Select the project to which you would like to add JoyDoc, then click Add Package
Select your application target and ensure that under Frameworks, Libraries, and Embedded Content you see JoyfillComponents listed
Manual Add
Get the latest version of the JoyfillComponents.xcframework and embed it into your application, for example by dragging and dropping the XCFramework bundle onto the Embed Frameworks build phase of your application target in Xcode. Make sure to enable Copy items if needed and Create groups.
Implement your code
🚧 Do not wrap JoyDoc component inside of a UIScrollView. JoyDoc rendering optimizations may not work properly and will introduce unintended bugs.
Example project
We recommend our UIKit/SwiftUI Example's in our repo to help get you started. This will show a readonly or fillable form view depending on the mode
you use. Learn more about modes
here.
Make sure to replace the userAccessToken
inside of Constants.swift file to see your documents you have inside your related Joyfill Manager account.
Code snippet
Make sure to replace the userAccessToken
inside of Constants.swift file to see your documents you have inside the. Note that the userAccessToken
can be retrieved using the Joyfill Manager and navigating to Settings & Users -> Access Tokens. Below is a simple quick example of it in action (we recommend using our Joyfill Example though as stated above).
Swift:
import UIKit
import JoyfillComponents
import Toast
class JoyDocViewController: UIViewController, onChange, UIImagePickerControllerDelegate & UINavigationControllerDelegate {
private let vm = JoyDocViewModel()
var docIdentifier: String = ""
private lazy var saveBtn: UIButton = {
var config = UIButton.Configuration.filled()
config.title = "Save"
config.baseBackgroundColor = .systemBlue.withAlphaComponent(0.08)
config.baseForegroundColor = .systemBlue
config.buttonSize = .medium
config.cornerStyle = .medium
let btn = UIButton(configuration: config)
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
override func viewDidLoad() {
super.viewDidLoad()
if let navigationController = navigationController {
joyfillNavigationController = navigationController
}
setup()
vm.delegate = self
vm.fetchJoyDoc(identifier: docIdentifier)
}
}
extension JoyDocViewController: JoyDocViewModelDelegate {
func didFinish() {
print("Joydoc retrieved did finish.")
self.title = "Document"
jsonData = vm.activeJoyDoc as! Data
DispatchQueue.main.async {
let joyfillForm = JoyfillForm()
joyfillForm.mode = "fill"
joyfillForm.saveDelegate = self
joyfillForm.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(joyfillForm)
NSLayoutConstraint.activate([
joyfillForm.topAnchor.constraint(equalTo: self.view.topAnchor, constant: -12),
joyfillForm.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
joyfillForm.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
joyfillForm.bottomAnchor.constraint(equalTo: self.saveBtn.topAnchor, constant: 0),
self.saveBtn.topAnchor.constraint(equalTo: joyfillForm.bottomAnchor, constant: -25),
self.saveBtn.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 25),
self.saveBtn.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -25),
self.saveBtn.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -30)
])
joyfillFormImageUpload = {
print("Upload images...")
var alertStyle = UIAlertController.Style.actionSheet
if (UIDevice.current.userInterfaceIdiom == .pad) {
alertStyle = UIAlertController.Style.alert
}
let alert = UIAlertController(title: "Choose Image", message: nil, preferredStyle: alertStyle)
alert.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { _ in
self.openImageGallery()
}))
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
func didFail(_ error: Error) {
print(error)
}
func handleOnChange(docChangelog: [String : Any], doc: [String : Any]) {
print("change: ", docChangelog)
}
func handleOnFocus(blurAndFocusParams: [String : Any]) {
print("focus: ", blurAndFocusParams)
}
func handleOnBlur(blurAndFocusParams: [String : Any]) {
print("blur: ", blurAndFocusParams)
}
func handleImageUploadAsync(images: [String]) {
print("images: ", images)
}
func openImageGallery() {
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){
let imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.allowsEditing = true
imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
self.present(imagePicker, animated: true, completion: nil)
} else {
let alert = UIAlertController(title: "Warning", message: "You don't have permission to access gallery.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
self.dismiss(animated: true, completion: nil)
if let pickedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
convertImageToDataURI(uri: pickedImage)
}
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
self.dismiss(animated: true, completion: nil)
}
func convertImageToDataURI(uri: UIImage) {
if let imageData = uri.jpegData(compressionQuality: 1.0) {
let base64String = imageData.base64EncodedString()
onUploadAsync(imageUrl: "data:image/jpeg;base64,\(base64String)")
}
}
@objc func didSave() {
vm.updateDocumentChangelogs(identifier: docIdentifier, docChangeLogs: docChangeLogs)
let toast = Toast.text("Form saved ✅")
toast.show()
navigationController?.popViewController(animated: true)
}
}
private extension JoyDocViewController {
func setup() {
navigationController?.navigationBar.prefersLargeTitles = false
view.backgroundColor = .white
view.overrideUserInterfaceStyle = .light
view.addSubview(saveBtn)
saveBtn.addTarget(self,
action: #selector(didSave),
for: .touchUpInside)
}
}
ViewModel:
import Foundation
import Alamofire
import SwiftyJSON
struct Constants {
static let baseURL = "https://api-joy.joyfill.io/v1/documents"
static let userAccessToken = "<replace_me>"
}
protocol JoyDocViewModelDelegate: AnyObject {
func didFinish()
func didFail(_ error: Error)
}
class JoyDocViewModel {
private(set) var activeJoyDoc: Any?
weak var delegate: JoyDocViewModelDelegate?
@MainActor
func fetchJoyDoc(identifier: String) {
Task { [weak self] in
let url = "\(Constants.baseURL)/\(identifier)"
print("Go get documents from \(url)")
let headers: HTTPHeaders = [
"Authorization": "Bearer \(Constants.userAccessToken)",
"Content-Type": "application/json"
]
AF.request(url, method: .get, headers: headers).validate().response { response in
switch response.result {
case .success(let value):
self?.activeJoyDoc = value
self?.delegate?.didFinish()
print("Success! Retrieved json (joydoc).")
case .failure(let error):
print(error)
}
}
}
}
@MainActor
func updateDocumentChangelogs(identifier: String, docChangeLogs: Any) {
do {
guard let url = URL(string: "\(Constants.baseURL)/\(identifier)/changelogs") else {
print("Invalid json url")
return
}
let jsonData = try JSONSerialization.data(withJSONObject: docChangeLogs, options: [])
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
request.setValue("Bearer \(Constants.userAccessToken)", forHTTPHeaderField: "Authorization")
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error updating changelogs: \(error)")
} else if let data = data {
let json = try? JSONSerialization.jsonObject(with: data, options: [.fragmentsAllowed])
let _ = json as? NSDictionary
}
}.resume()
} catch {
print("Error serializing JSON: \(error)")
}
}
}
See parameters for the JoyfillForm SDK instance.
Swift form builder guide summary
This guide serves as a comprehensive resource for developers looking to seamlessly integrate the JoyDoc iOS SDK into Swift projects, with a specific emphasis on UIKit. The inclusion of detailed instructions for both Swift Package Manager and manual installation ensures flexibility in the integration process, catering to diverse developer preferences.
A noteworthy caution is provided, advising against enclosing JoyDoc components within a UIScrollView to avoid potential rendering issues and unintended bugs. The article strongly encourages developers to explore the example projects available in the repository, specifically designed for both SwiftUI and UIKit. These examples showcase readonly and fillable form views, providing practical insights into the usage of Joyfill components.
To facilitate a quick start, a code snippet is shared, underscoring the importance of replacing the userAccessToken in the Constants.swift file. Obtaining this token from the Joyfill Manager, specifically from Settings & Users -> Access Tokens, enables developers to seamlessly access their documents within the Joyfill Manager account.
In essence, this guide not only provides step-by-step instructions for integration but also emphasizes best practices and practical examples, aiming to empower developers with a robust understanding of the JoyDoc iOS SDK for efficient and effective utilization in their Swift projects.
Related Articles
You may also be interested in reading the following related form builder guides:
Embed a form builder with Javascript
Embed a form builder with React
Embed a form builder with React Native
Embed a form builder with Angular
Embed a form builder with Kotlin