Struggling to implement PDFView in SwiftUI - ios

I am trying to display a PDF in a SwiftUI view.
When trying to implement other solutions here, I get the error Missing arguments for parameters 'PDFName', 'DisplayName' in call
Errors image
I assume that the way you use PDFView() has changed with iOS 15; but I can't seem to find any way to use it in SwiftUI or the docs.
Any help would be greatly appreciated.
Many thanks in advance!
Current attempt at implementation resulting in above error:
import SwiftUI
import PDFKit
struct PDFViewer: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
pdfView.autoScales = true
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
}
}
struct PDFView_Previews: PreviewProvider {
static var previews: some View {
PDFViewer(url: Bundle.main.url(forResource: "somePDF", withExtension: "pdf"))
}
}

Related

How to navigate between two API parameters in SwiftUI

I'm beginner of iOS app development, currently doing iOS & Swift Bootcamp on Udemy by Angela Yu. I have this app called H4X0R News, which shows Hacker News all stories that are on the front/home page on the app by using its API. By the end of a module the app works fine when url property from API json is not nil but there are certain cases when url equals nil. These are posts which instead has story_text property. So what I want here to adjust is add story_text to my code and use it to navigate between this and url parameter. Here's the code I've got:
ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject var networkManager = NetworkManager()
var body: some View {
NavigationView {
List(networkManager.posts) { post in
NavigationLink(destination: DetailView(url: post.url)) {
HStack {
Text(String(post.points))
Text(post.title)
}
}
}
.navigationTitle("H4X0R NEWS")
}
.onAppear {
self.networkManager.fechData()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
WebView.swift
import Foundation
import WebKit
import SwiftUI
struct WebView: UIViewRepresentable {
let urlString: String?
func makeUIView(context: Context) -> WKWebView {
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context) {
if let safeString = urlString {
if let url = URL(string: safeString) {
let request = URLRequest(url: url)
uiView.load(request)
}
}
}
}
DetailView.swift
import SwiftUI
struct DetailView: View {
let url: String?
var body: some View {
WebView(urlString: url)
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
DetailView(url: "https://www.google.com/")
}
}
NetworkManager.swift
import Foundation
class NetworkManager: ObservableObject {
#Published var posts = [Post]()
func fechData() {
if let url = URL(string: "http://hn.algolia.com/api/v1/search?tags=front_page") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { data, response, error in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(Results.self, from: safeData)
DispatchQueue.main.async {
self.posts = results.hits
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
PostData.swift
import Foundation
struct Results: Decodable {
let hits: [Post]
}
struct Post: Decodable, Identifiable {
var id: String {
return objectID
}
let objectID: String
let points: Int
let title: String
let url: String?
}
So what I'm sure I need to add this story_text to PostData as String? and then make conditional statement in the WebView.updateUIView() function. Then update the code in other files. But like I said, I'm new in the programming world and I seek for help here for the first time since I've started the course.
If I understand you correct, you want to navigate to a simple text view if there is no URL for the Post?
So you can do it like this:
let text: String?
var body: some View {
if let url = url {
WebView(urlString: url)
} else {
Text(text ?? "-")
}
}

SwiftUI Preview Canvas doesn't load Decoded Struct data from local file

I'm trying to get SwiftUI Previews working (with already downloaded API data) so that I don't need to run the app in simulator. Not sure what is wrong/how to go about it.
I've seen a few YouTube and (a lot) of SO on how to get this done but still hitting a wall. Appreciate pointers. (Note: 1st time trying to learn SwiftUI by porting a view of my app)
The data is going to be read-only, so there's no use for #State or #Bindings I believe and shouldn't be part of the #EnvironmentObject as well?
The YouTube's I've seen and google links are mainly using (locally stored) JSON files and they work.
eg: https://www.youtube.com/watch?v=hfjZNwayXfg and https://www.youtube.com/watch?v=EycwLxTU-EA
#available(iOS 13, *)
struct avatarView: View {
let weeklySummary: [HelperIntervalsIcu.icuWeeklyData]
func getAvatarPic() -> UIImage? {
if let avatarUrl = weeklySummary.last?.icuAvatar {
let avatarPicName = URL(fileURLWithPath: avatarUrl).lastPathComponent
let avatarPicImage = HelperIntervalsIcu.loadAvatarPic(fileName: avatarPicName)
return avatarPicImage
}
return nil
}
func getUserName() -> String {
if let userName = weeklySummary.last?.icuName {
return userName.prefix(1).capitalized + userName.dropFirst()
}
return "User Name Placeholder"
}
var body: some View {
HStack {
if let image = getAvatarPic() {
Image(uiImage: image)
} else {
Image("profile-200x200")
}
Text(getUserName())
Spacer()
}
}
}
#available(iOS 13.0, *)
struct IntervalsWeeklyView_Previews: PreviewProvider {
static var previews: some View {
let weeklySummary = HelperIntervalsIcu.loadWeeklySummaryFromFile()
avatarView(weeklySummary: weeklySummary)
}
}
The Preview just shows this
But when running in the simulator, it will work.
The data within HelperIntervalsIcu.icuWeeklyData is from a struct
struct icuWeeklyData : Codable {
var icuName : String
var icuAvatar : String
}

Downloading public PDF file from S3 Bucket SwiftUI

I am simply trying to store a static PDF in an S3 bucket which I can grab and present using Apple's built-in PDF Viewer. I am a having problems and a bit confused on a way to store the PDF locally in the proper form. I apologize if this is repeated or too simple, I have searched for hours on a proper solution but have not found anything that works. Thank you. I tried using the URL directly but that also threw an error.
import Amplify
import SwiftUI
import WebKit
struct ContentView: View {
var body: some View {
containedView()
}
func grabPDF(){
Amplify.Storage.downloadData(
key: "TermsOfUse.pdf"
){ result in
switch result{
case .success(let key):
print("File with key: \(key)")
case .failure(let storageError):
print("Failed: ", storageError)
}
}
}
func containedView() -> WebView{
grabPDF()
return WebView(request: openPDF())
}
func openPDF() -> URLRequest{
let path = Bundle.main.path(forResource: "TermsOfUse", ofType: "pdf")
let url = URL(fileURLWithPath: path!)
print(url)
return URLRequest(url: url)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct WebView: UIViewRepresentable{
let request: URLRequest
func makeUIView(context: Context) -> WKWebView{
return WKWebView()
}
func updateUIView(_ uiView: WKWebView, context: Context){
uiView.load(request)
}
}
Amplify.Storage.downloadData returns a Data object -- it doesn't actually download the file. But, there's another method downloadFile that will work for this.
Your applications Bundle has a static set of files. Once you save a file, you won't be looking in the Bundle, but rather in the app's documents or temp directory.
Rather than making the Amplify calls in the view body, probably better to assign it to a separate object (here I'm using an ObservableObject called DataLoader) to do the work at then set a flag (downloaded) when it's done.
class DataLoader : ObservableObject {
#Published var downloaded : Bool = false
func makeRequest() {
let downloadToFileName = getTermsOfUseURL()
Amplify.Storage.downloadFile(
key: "TermsOfUse.pdf",
local: downloadToFileName,
progressListener: { progress in
print("Progress: \(progress)")
}, resultListener: { event in
switch event {
case .success:
print("Completed")
DispatchQueue.main.async { self.downloaded = true }
case .failure(let storageError):
print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)")
}
})
}
func getTermsOfUseURL() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("TermsOfUse.pdf")
}
}
struct ContentView: View {
#ObservedObject var loader = DataLoader()
var body: some View {
VStack {
if loader.downloaded {
WebView(request: URLRequest(url: loader.getTermsOfUseURL()))
}
}.onAppear {
loader.makeRequest()
}
}
}
Note: I have not tested the Amplify code; it is assumed to work as it is taken from https://docs.amplify.aws/lib/storage/download/q/platform/ios

How to implement PDF Viewer to SwiftUI application?

I'm building an app, which displays PDFs, but I don't know how to display a PDF file using SwiftUI. I found tutorials on how to display a PDF file using UIKit, but there are no tutorials about SwiftUI. Can anyone help me?
I'm also trying to do that using MVVM design pattern. If there's someone, who can help me, I will be extremely grateful!
Code:
HomeView.swift
import SwiftUI
struct HomeView: View {
var deeds: [Deed] = deedsData
var body: some View {
NavigationView {
List(deeds) { item in
Button(action: {
}) {
HStack {
Image(systemName: "doc.fill")
Text(item.title)
}
}
}
.navigationTitle("title")
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView(deeds: deedsData)
}
}
DeedModel.swift
import SwiftUI
struct Deed: Identifiable {
var id = UUID()
var title: String
var URL: String
}
let deedsData: [Deed] = [
Deed(title: NSLocalizedString("civilCode", comment: "Civil code"), URL: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19640160093/U/D19640093Lj.pdf"),
Deed(title: NSLocalizedString("penalCode", comment: "Penal code"), URL: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19970880553/U/D19970553Lj.pdf"),
Deed(title: NSLocalizedString("civilProcedureCode", comment: "Code of civil procedure"), URL: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19640430296/U/D19640296Lj.pdf"),
Deed(title: NSLocalizedString("familyAndGuardianshipCode", comment: "Family and guardianship code"), URL: "http://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19640090059/U/D19640059Lj.pdf"),
Deed(title: NSLocalizedString("laborCode", comment: "Labor code"), URL: "https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU19740240141/U/D19740141Lj.pdf"),
]
Anyone knows how can I do that in MVVM pattern?
To display a PDF with Apple-only frameworks, you'll need to use UIKit, via a UIViewRepresentable:
import PDFKit
import SwiftUI
struct PDFKitRepresentedView: UIViewRepresentable {
typealias UIViewType = PDFView
let data: Data
let singlePage: Bool
init(_ data: Data, singlePage: Bool = false) {
self.data = data
self.singlePage = singlePage
}
func makeUIView(context _: UIViewRepresentableContext<PDFKitRepresentedView>) -> UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView()
pdfView.document = PDFDocument(data: data)
pdfView.autoScales = true
if singlePage {
pdfView.displayMode = .singlePage
}
return pdfView
}
func updateUIView(_ pdfView: UIViewType, context _: UIViewRepresentableContext<PDFKitRepresentedView>) {
pdfView.document = PDFDocument(data: data)
}
}
then PDFKitRepresentedView can be used in your view hierarchy.
This takes a Data object as input, so you'll need to convert those URL objects you have to Data via a network call first. So, you might do something like:
#State var data : Data?
...
.onAppear {
self.data = try? Data(contentsOf: url)
}
Keep in mind this is vastly simplified -- you'll want to do some error handling, etc. Might want to do some searching on SwiftUI network calls.

Content view is not updating when the variable is changed

I have basic app that display a website and when I press a button it should send me to another link but it does not idk why I tried using #state bool and changing it when button is pressed but no use
the website loads but the button does not change the website
ContentView.swift
import SwiftUI
import WebKit
import Foundation
struct ContentView: View {
#State private var selectedSegment = 0
#State var websi = "172.20.10.3"
#State private var websites = ["192.168.8.125", "192.168.8.125/Receipts.php","192.168.8.125/myqr.php"]
#State private var sssa = ["Home","MyRecipts","MyQr"]
#State var updater: Bool
var body: some View {
HStack {
NavigationView{
VStack{
Button(action: {
self.websi = "172.20.10.3/myqe.php"
self.updater.toggle()
}) {
Text(/*#START_MENU_TOKEN#*/"Button"/*#END_MENU_TOKEN#*/)
} .pickerStyle(SegmentedPickerStyle());
/* Text("Selected value is: \(websites[selectedSegment])").padding()*/
Webview(url: "http://\(websi)")
}
}
.padding(.top, -44.0)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView( updater: false)
.padding(.top, -68.0)
}
}
Webview.swift
import Foundation
import SwiftUI
import WebKit
struct Webview: UIViewRepresentable {
var url: String
func makeUIView(context: Context) -> WKWebView {
guard let url = URL(string: self.url) else{
return WKWebView()
}
let request = URLRequest(url: url)
let wkWebView = WKWebView()
wkWebView.load(request)
return wkWebView
}
func updateUIView(_ uiView: WKWebView, context:
UIViewRepresentableContext<Webview>){
}
}
Because you are not recalling the WebView when the websi changes it won't do anything. You need to add a #Binding tag to the url so that it knows to update. Then you can call
WebView(self.$websi)
Here is a possible solution using an #ObservableObject:
struct WebView: UIViewRepresentable {
#ObservedObject var viewModel: ViewModel
func makeUIView(context: UIViewRepresentableContext<WebView>) -> WKWebView {
return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: UIViewRepresentableContext<WebView>) {
if let url = URL(string: viewModel.url) {
webView.load(URLRequest(url: url))
}
}
}
extension WebView {
class ViewModel: ObservableObject {
#Published var url: String
init(url: String) {
self.url = url
}
}
}
struct ContentView: View {
#State private var currentWebsite = "https://google.com"
var body: some View {
VStack {
Button("Change") {
self.currentWebsite = "https://apple.com"
}
WebView(viewModel: .init(url: currentWebsite))
}
}
}

Resources