I have a pretty tricky problem which I’m trying to figure out so be warned. So to start off, I have a view called ExcelView in which its able to create an excel type of view. In the ExcelView it it has locations on one axis and supply names on another. This view allows the user to be able to scroll through all four cardinal directions and it displays the qty of supplies per location, for the moment I left all the cells as N/A. Now, I currently made a file called “Extensions” which is responsible for creating a PDF of the current screen and saving it. However, since I allow the Excel View to be able to move in 4 directions, whenever the current screen is made into a PDF, it will only convert the current screen to a PDF. I want to be able to have the whole excel view on one page. A good example of what I want can be see from the picture I attached. Now, originally I tried messing with the frame of the PDF to see if I could “Mold” it to the excel view, I also tried created a function called getsafeArea to see if I could eliminate the safe area on specific parts of the screen, but I ended up getting confused. Let me know if you have any questions. Thanks Again.
ExcelView:
import SwiftUI
@available(iOS 16.0, *)
struct ExcelView: View {
@EnvironmentObject var dataManager: DataManager
@Environment(\.verticalSizeClass) var heightSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var widthSizeClass: UserInterfaceSizeClass?
let columns = 5
let rows = 20
@State private var offset = CGPoint.zero
var excelNames = ["Stassney","SM", "Cameron", "Lamar", "Riverside"]
var excelSuppliesNames = ["Pencil", "Pen", "Highlighter", "Fine Sharpie Pen", "Big Sharpie Pen", "Post-it", "Finger Tip Moist.", "Regular Paper Clip", "Big Paper Clip", "Regular Tape", "Packaging Tape", "Regular Rubber Bands", "Big Rubber Bands", "Regular Staples", "Big Staples", "Paper", "Yellow Filing Folders", "Hand Sanitizer", "Wireless Mouse", "Wireless Keyboard"]
var body: some View {
VStack(alignment: .leading, spacing: 0){
buttonHeader
HStack(alignment: .top, spacing: 0) {
VStack(alignment: .leading, spacing: 0) {
// empty corner
Color.clear.frame(width: 70, height: 50)
ScrollView([.vertical]) {
rowsHeader
.offset(y: offset.y)
}
.disabled(true)
}
VStack(alignment: .leading, spacing: 0) {
ScrollView([.horizontal]) {
colsHeader
.offset(x: offset.x)
}
.disabled(true)
table
.coordinateSpace(name: "scroll")
}
}
}
// 1st Option: landscape, 2nd Option: portrait
.frame(maxWidth: heightSizeClass == .compact ? UIScreen.screenwidth + 335 : UIScreen.screenwidth, maxHeight: heightSizeClass == .compact ? UIScreen.screenHeight / 2.3 : UIScreen.screenHeight)
}
var buttonHeader: some View {
VStack {
HStack {
Button {
exportPDF {
self
.environmentObject(dataManager)
} completion: { status, url in
if let url = url, status {
dataManager.PDFUrl = url
dataManager.showShareSheet.toggle()
}
else {
print("Failed to produce PDF")
}
}
} label: {
Image(systemName: "square.and.arrow.up.fill")
.font(.system(size: 45))
.foregroundColor(Color.black.opacity(0.7))
}
}
}
.sheet(isPresented: $dataManager.showShareSheet) {
dataManager.PDFUrl = nil
} content: {
if let PDFUrl = dataManager.PDFUrl {
ShareSheet(urls: [PDFUrl])
}
}
}
var colsHeader: some View {
HStack(alignment: .center, spacing: 0) {
//The number of col/row must match the # of items in array
ForEach(0..<columns) { col in
Text(excelNames[col])
.foregroundColor(.black)
.font(.system(size: 15))
.frame(width: 100, height: 50)
.border(Color.blue)
}
}
}
var rowsHeader: some View {
VStack(alignment: .leading, spacing: 0) {
ForEach(0..<rows) { row in
Text("\(excelSuppliesNames[row] )")
.foregroundColor(.black)
.font(.caption)
.frame(width: 100, height: 50)
.border(Color.blue)
}
}
}
var table: some View {
ScrollView([.vertical, .horizontal]) {
VStack(alignment: .leading, spacing: 0) {
ForEach(0..<rows) { row in
HStack(alignment: .top, spacing: 0) {
ForEach(0..<columns) { col in
Text("N/A")
.font(.system(size: 12))
.frame(width: 100, height: 50)
.border(Color.blue)
}
}
}
}
.background( GeometryReader { geo in
Color.clear
.preference(key: ViewOffsetKey.self, value: geo.frame(in: .named("scroll")).origin)
})
.onPreferenceChange(ViewOffsetKey.self) { value in
//print("offset >> \(value)")
offset = value
}
}
}
}
struct ViewOffsetKey: PreferenceKey {
typealias Value = CGPoint
static var defaultValue = CGPoint.zero
static func reduce(value: inout Value, nextValue: () -> Value) {
value.x += nextValue().x
value.y += nextValue().y
}
}
@available(iOS 16.0, *)
struct ExcelView_Previews: PreviewProvider {
static var previews: some View {
ExcelView().environmentObject(DataManager())
}
}
//Share Sheet
struct ShareSheet: UIViewControllerRepresentable {
var urls: [Any]
func makeUIViewController(context: Context) -> UIActivityViewController {
let controller = UIActivityViewController(activityItems: urls, applicationActivities: nil)
return controller
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
}
extension UIScreen {
static let screenwidth = UIScreen.main.bounds.size.width
static let screenHeight = UIScreen.main.bounds.size.height
static let screenSize = UIScreen.main.bounds.size
}
Extensions:
import SwiftUI
extension View {
//Extracting Views Height and width with the help of Hosting Controller and ScrollView
func converToScrollView<Content: View>(@ViewBuilder content: @escaping ()->Content) -> UIScrollView {
let scrollView = UIScrollView()
// Convertin SwiftUI View to UIKit View
let hostingController = UIHostingController(rootView: content()).view!
hostingController.translatesAutoresizingMaskIntoConstraints = false
//Constraints
let constraints = [
hostingController.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
hostingController.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
hostingController.topAnchor.constraint(equalTo: scrollView.topAnchor),
hostingController.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
//Width Anchor
hostingController.widthAnchor.constraint(equalToConstant: screenBounds().width)
]
scrollView.addSubview(hostingController)
scrollView.addConstraints(constraints)
scrollView.layoutIfNeeded()
return scrollView
}
//Export to PDF
//Completion Handler will send Status and URL
func exportPDF<Content: View>(@ViewBuilder content: @escaping ()->Content, completion: @escaping (Bool, URL?)->()) {
//Temp URL
let documentDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
//To Generate New File when ever its generated
let outputFileURL = documentDirectory.appendingPathComponent("PDFName \(UUID().uuidString).pdf")
let pdfView = converToScrollView {
content()
}
pdfView.tag = 1009
let size = pdfView.contentSize
//Removing Safe Area top Value
pdfView.frame = CGRect(x: 0, y: 0, width: size.width, height: size.height)
//Attaching to Root View and redering the pdf
getRootController().view.insertSubview(pdfView, at: 0)
//Rendering PDF
let renderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: size.width, height: size.height))
do {
try renderer.writePDF(to: outputFileURL, withActions: { context in
context.beginPage()
pdfView.layer.render(in: context.cgContext)
})
completion(true , outputFileURL)
}
catch {
completion(false, nil)
print(error.localizedDescription)
}
// Removing the added View
getRootController().view.subviews.forEach { view in
if view.tag == 1009 {
print("Removed")
view.removeFromSuperview()
}
}
}
func screenBounds() -> CGRect {
return UIScreen.main.bounds
}
func getRootController()->UIViewController {
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .init()
}
guard let root = screen.windows.first?.rootViewController else {
return .init()
}
return root
}
func getSafeArea() -> UIEdgeInsets{
guard let screen = UIApplication.shared.connectedScenes.first as? UIWindowScene else {
return .zero
}
guard let safeArea = screen.windows.first?.safeAreaInsets else {
return .zero
}
return safeArea
}
}
DataManager:
import Foundation
class DataManager: ObservableObject {
//PDF Properties
@Published var PDFUrl: URL?
@Published var showShareSheet: Bool = false
}
[Goal of project][1]
[1]: https://i.stack.imgur.com/H0Onr.png