We have requirement adding usdz file to UIView and showing the it’s content and archive the data and save to file. When user open the file, we need to unarchive that usdz content and binding with UIView and showing it to user. Initially, we created SCNScene object passing usdz file url like below.
do {
usdzScene = try SCNScene(url: usdzUrl)
} catch let error as NSError {
print(error)
}
Since SCNScene support to NSSecureCoding protocol , we directly archive that object and save it file and load back it from file and created SCNScene object using NSKeyedUnarchiver process.
But for some files, we realised high memory consumption while archiving the SCNScene object using below line.
func encode(with coder: NSCoder) {
coder.encode(self.scnScene, forKey: "scnScene")
}
File referene link : toy_drummer_idle.usdz
When we analyse apple documentation (check discussion section) , it said, scn file extension is the fastest format for processing than the usdz.
So we used SCNSecne write to feature for creating scn file from given usdz file.
After that, When we do the archive SCNScene object that was created by sun file url, the archive process is more faster and it will not take high memory as well. It is really faster previous case now.
But unfortunately, SCNScene write method will take lot of time for this conversion and memory meter is also going high and it will be caused to app crash as well.
I check the output file size as well. The given usdz file size is 18MB and generated scn file size is 483 MB. But SCNScene archive process is so fast.
Please, analyse this case and please, provide some guideline how we can optimise this behaviour. I really appreciate your feedback.
Full Code:
import UIKit
import SceneKit
class ViewController: UIViewController {
var scnView: SCNView?
var usdzScene: SCNScene?
var scnScene: SCNScene?
lazy var exportButton: UIButton = {
let btn = UIButton(type: UIButton.ButtonType.system)
btn.tag = 1
btn.backgroundColor = UIColor.blue
btn.addTarget(self, action: #selector(buttonPressed(_:)), for: .touchUpInside)
btn.setTitle("USDZ to SCN", for: .normal)
btn.setTitleColor(.white, for: .normal)
btn.layer.borderColor = UIColor.gray.cgColor
btn.titleLabel?.font = .systemFont(ofSize: 20)
btn.translatesAutoresizingMaskIntoConstraints = false
return btn
}()
func deleteTempDirectory(directoryName: String) {
let tempDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory())
let tempDirectory = tempDirectoryUrl.appendingPathComponent(directoryName, isDirectory: true)
if FileManager.default.fileExists(atPath: URL(string: tempDirectory.absoluteString)!.path) {
do{
try FileManager.default.removeItem(at: tempDirectory)
}
catch let error as NSError {
print(error)
}
}
}
func createTempDirectory(directoryName: String) -> URL? {
let tempDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory())
let toBeCreatedDirectoryUrl = tempDirectoryUrl.appendingPathComponent(directoryName, isDirectory: true)
if !FileManager.default.fileExists(atPath: URL(string: toBeCreatedDirectoryUrl.absoluteString)!.path) {
do{
try FileManager.default.createDirectory(at: toBeCreatedDirectoryUrl, withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError {
print(error)
return nil
}
}
return toBeCreatedDirectoryUrl
}
@IBAction func buttonPressed(_ sender: UIButton){
let scnFolderName = "SCN"
let scnFileName = "3D"
deleteTempDirectory(directoryName: scnFolderName)
guard let scnDirectoryUrl = createTempDirectory(directoryName: scnFolderName) else {return}
let scnFileUrl = scnDirectoryUrl.appendingPathComponent(scnFileName).appendingPathExtension("scn")
guard let usdzScene else {return}
let result = usdzScene.write(to: scnFileUrl, options: nil, delegate: nil, progressHandler: nil)
if (result) {
print("exporting process is success.")
} else {
print("exporting process is failed.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
let usdzUrl: URL? = Bundle.main.url(forResource: "toy_drummer_idle", withExtension: "usdz")
guard let usdzUrl else {return}
do {
usdzScene = try SCNScene(url: usdzUrl)
} catch let error as NSError {
print(error)
}
guard let usdzScene else {return}
scnView = SCNView(frame: .zero)
guard let scnView else {return}
scnView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(scnView)
self.view.addSubview(exportButton)
NSLayoutConstraint.activate([
scnView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
scnView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
scnView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
scnView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -30),
exportButton.widthAnchor.constraint(equalToConstant: 200),
exportButton.heightAnchor.constraint(equalToConstant: 40),
exportButton.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
exportButton.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {[weak self] in
guard let self else {return}
loadModel(scene: usdzScene)
}
}
func loadModel(scene: SCNScene){
guard let scnView else {return}
scnView.autoenablesDefaultLighting = true
scnView.scene = scene
scnView.allowsCameraControl = true
}
}
![]("https://developer.apple.com/forums/content/attachment/5ff0197b-7e0b-4efe-b295-cc205c73dc02" "title=Screenshot 2024-06-20 at 12.23.40.png;width=1712;height=1013")
Post
Replies
Boosts
Views
Activity
I tried to find, how to set up minimum deployment target version separately for iPad and iPhone and but, I was unable to find the way.
According to current xocde configuration, we cannot set that value device specifically.
If anyone has an idea about it, please, share it and I really appreciate your help and feedback.
The pdf that has external weblink on the first page for accessing they youtube. When i open it pdf of reader or other apps it will be asked open the youtube. I want to support that functionality in UIView level. I copied PDF content to UIView successfully and but cannot see any action, once i click the external web link.
Here is the my code steps:
Storing pdf file in project level (Doc.pdf)
Take the first page and convert that page to Data. (During the practical scenario, i need to store that value as Data first, it will be needed reuse later, So cannot use PDFPage object for processing directly.).
Binding Data object with CanvasView and override the CanvasView draw method for copying pdf data to UIView. (During practical scenario, I can't use PDFView object, I have to use custom UIView and it will be needed support other customize operation (ex: erasing, cut, copy etc)
Finally, even though all contents are copied successfully, i cannot see any action, once i clicked the external web link.
Here is the code example:
ViewController.Swift
import PDFKit
class ViewController: UIViewController {
var pdfDocument: PDFDocument?
override func loadView() {
super.loadView()
}
func getData(pdfPage: PDFPage) -> Data {
let cropBox = pdfPage.bounds(for: .cropBox)
var adjustedCropBox = cropBox
if ((pdfPage.rotation == 90) || (pdfPage.rotation == 270) || (pdfPage.rotation == -90)) {
adjustedCropBox.size = CGSize(width: cropBox.height, height: cropBox.width)
}
let renderer = UIGraphicsPDFRenderer(bounds: adjustedCropBox)
return renderer.pdfData { (ctx) in
ctx.beginPage()
ctx.cgContext.setFillColor(UIColor.white.cgColor)
ctx.fill(adjustedCropBox)
pdfPage.transform(ctx.cgContext, for: .cropBox)
switch pdfPage.rotation {
case 0:
ctx.cgContext.translateBy(x: 0, y: adjustedCropBox.height)
ctx.cgContext.scaleBy(x: 1, y: -1)
case 90:
ctx.cgContext.scaleBy(x: 1, y: -1)
ctx.cgContext.rotate(by: -.pi / 2)
case 180, -180:
ctx.cgContext.scaleBy(x: 1, y: -1)
ctx.cgContext.translateBy(x: adjustedCropBox.width, y: 0)
ctx.cgContext.rotate(by: .pi)
case 270, -90:
ctx.cgContext.translateBy(x: adjustedCropBox.height, y: adjustedCropBox.width)
ctx.cgContext.rotate(by: .pi / 2)
ctx.cgContext.scaleBy(x: -1, y: 1)
default:
break
}
pdfPage.draw(with: .cropBox, to: ctx.cgContext)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let fileUrl = Bundle.main.url(forResource: "Doc", withExtension: "pdf")
guard let fileUrl else {
return
}
pdfDocument = PDFDocument(url: fileUrl)
let firstPage: PDFPage? = pdfDocument?.page(at: 0)
print("first page annotation count \(firstPage?.annotations.count)")
guard let firstPage else {
return
}
let pdfData = getData(pdfPage: firstPage)
let canvasView = CanvasView(data: pdfData)
self.view.addSubview(canvasView)
NSLayoutConstraint.activate([
canvasView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
canvasView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, constant: 0),
canvasView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
canvasView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, constant: 0)
])
}
}
CanvasView.Swift
import PDFKit
class CanvasView: UIView {
var page: PDFPage?
init(data: Data) {
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
page = loadPDFPage(pdfData: data)
}
init(pdfPage: PDFPage){
super.init(frame: .zero)
translatesAutoresizingMaskIntoConstraints = false
page = pdfPage
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadPDFPage(pdfData: Data) -> PDFPage? {
guard let document = PDFDocument(data: pdfData) else {
return nil
}
return document.page(at: 0)
}
override func draw(_ layer: CALayer, in ctx: CGContext) {
guard let page else {
return
}
print("num of annotations - \(page.annotations.count)")
if let cgPDFPage = page.pageRef {
let cropBoxBounds = page.bounds(for: .cropBox)
print(page.displaysAnnotations)
let scaleX = layer.bounds.width / cropBoxBounds.width
let scaleY = layer.bounds.height / cropBoxBounds.height
ctx.saveGState()
ctx.scaleBy(x: scaleX, y: scaleY)
ctx.translateBy(x: -cropBoxBounds.origin.x, y: cropBoxBounds.height + cropBoxBounds.origin.y )
ctx.scaleBy(x: 1, y: -1)
ctx.setFillColor(UIColor.white.cgColor)
ctx.fill(cropBoxBounds)
ctx.drawPDFPage(cgPDFPage)
ctx.restoreGState()
}
}
override func draw(_ rect: CGRect) {}
}
Here the project
Special Note: Once i loaded PDFPage from url, it has two link annotations. Once i created PDFPage from Data in CavansView, i cannot see any PDF annotations and it is empty. So I assume, during data conversion process, annotations will not be considered. even though i pass original PDFPage object directly instead of Data, link click actions are not worked.
So i need to someone help from how we send PDFAnnotation object data to Data object and How we can support web link behaviour as we expected.
Please, help me to resolve this case and i really appreciate your feedback and help.
We are going to integrate Room Plan Feature to our existing application.
I didn't find any pricing plan section for Room Plan feature.
Is this feature totally free or is it has a pricing plan?
If it has pricing plan, can someone share about it ?
Room Plan feature is not Beta version right?
Can someone advise us? I really appreciate your feedback and help.