Unable to reload/redisplay PDF in WkWebView; but RTF apparently works - pdf

Scenario: TabbedUI with Tab #1 being a WkWebView displaying a PDF of general information (Info).
Problem: When I exit tab #1 (info) for another tab (ex. search), and return....I get an empty PDF with the following error message in the console:
Could not signal service com.apple.WebKit.WebContent: 113: Could not
find specified service
However this doesn't happen when I use a standard .rtf (Rich Text Format) file.
Here's my code:
import SwiftUI
import WebKit
struct IntroSwiftUI: View {
var body: some View {
Webview()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.bottom)
}
}
// =====================================================================================================
struct Webview: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
guard let url = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") else {
print("Inside Webview: unable to reload ReadMe.")
return WKWebView()
}
let request = URLRequest(url: url)
let wkWebview = WKWebView()
wkWebview.load(request)
wkWebview.scrollView.bounces = false
return wkWebview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
I just want a static PDF to be available for viewing whenever the user which to read some documentation (with embedded images). But apparently it tries to rebuild and gets lost.
Do I need to do something 'special' with the PDF, like release it when exiting & re-create it upon return? That seems totally inefficient.

Per suggestion,
Use the PDFView paradigm.
Here is my revised (correct) code for PDF viewing:
import SwiftUI
import UIKit
import PDFKit
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
// =====================================================================================================
struct IntroSwiftUI: View {
var body: some View {
if let documentURL = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") {
VStack(alignment: .center) {
Text("Introduction")
.font(.title3)
.fontWeight(Font.Weight.medium)
PDFKitView(url: documentURL)
}
} else {
Text("Sorry, No PDF")
}
}
}

Related

.isFavorite results in errors

I'm trying to place an inFavorite on a Detailed View but get the following errors:
-Cannot convert value of type 'Binding' to expected condition type 'Bool'
-Value of type 'ObservedObject.Wrapper' has no dynamic member 'isFavorite' using key path from root type 'TitleModel'
import SwiftUI
struct DetailView: View {
#EnvironmentObject var model: TitleModel
var detail: Title
var body: some View {
ScrollView {
VStack (alignment: .leading) {
//MARK: Detail Image
Image(detail.image1)
.resizable()
.scaledToFill()
//MARK: Divider
Divider()
if $model.isFavorite {
Image(systemName: "star.fill")
.imageScale(.medium)
.foregroundColor(.yellow)
}
Divider()
//MARK: Remark
VStack(alignment: .leading) {
Text("Remark:")
.font(.headline)
.padding([.bottom, .top], 5)
ForEach(detail.remark, id:\.self) {item in
Text("• " + item)
}
}
.padding(.horizontal)
//MARK: Reference(s)
VStack(alignment: .leading) {
Text("Reference(s):")
.font(.headline)
.padding([.bottom, .top], 5)
}
.padding(.horizontal)
}
}
.navigationBarTitle(detail.title)
}
}
struct DetailView_Previews: PreviewProvider {
static var previews: some View {
//create a dummy title and pass it into the detail view so we can see a preview
let model = TitleModel()
DetailView(detail: model.titles[0])
}
}
The view works perfect until I throw in the .isFavorite code. 'var model' references my View Model for my JSON data 'titles'. I am attempting to allow the person to tap the .star to highlight the view. I will then have a Scroll View with only the favorites. Thanks for helping me out.

NSApp: Hide main window until proper ViewController can be loaded

I have a MacOS app that is a registered custom URL handler.
I'm trying to make it show a specific NSViewController if the app is started via the url handler or the regular window and ViewController if no parameters are used.
In my AppDelegate.swift I have this:
func application(_ application: NSApplication, open urls: [URL]) {
AppDelegate.externalCaller = true;
NSApp.hide(self)
let url: URL = urls[0];
// Process the URL.
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true);
let method = components?.host;
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if (method == "DO_STH") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "DO_STH_2") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "PROCESS_STUFF") {
// Show window
DispatchQueue.main.async {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let restoringViewController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("restoringData")) as! RestoringViewController;
if let window = NSApp.mainWindow {
window.contentViewController = restoringViewController;
}
NSApp.activate(ignoringOtherApps: true);
AppDelegate.restoreData();
}
}
}
}
}
The problem is that by the time NSApp.hide(self) runs, there's already a window visible with the default viewController, creating some flickering effect.
What's the best way to make the app start without any visible window by default and only show the window if it wasn't started with any URL parameter and later on demand if a specific URL parameter exists?
Unchecking "Is initial Controller" and adding this to AppDelegate solved my issue
func applicationDidFinishLaunching(_ notification: Notification) {
if (!AppDelegate.externalCaller) {
showWindow();
}
}
func showWindow() {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let windowController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MainWindowController")) as! NSWindowController;
windowController.showWindow(self);
}

SwiftUI with ObservableObject - List having NavigationLink to details downloading it on scroll NOT on appear

I have list of cities
struct CityListView: View {
#ObservedObject private(set) var citiesViewModel: CitiesViewModel
var body: some View {
LoadingView(isShowing: .constant(citiesViewModel.cities?.isEmpty ?? false)) {
NavigationView {
List(self.citiesViewModel.cities ?? []) { city in
NavigationLink(destination: DetailView(cityName: city.name,
detailCityModel: DetailCityModel(cityId: city.id))) {
Text(city.name)
}
}
.navigationBarTitle(Text("Cities"), displayMode: .large)
}
}
}
}
and when I'm scrolling the list, the DetailCityModel inits and download data from API. How to downloading (or init DetailCityModel) on DetailView's appearance, not for showing item with NAvigationLink to DetailView?
You have to kick off the API call in onAppear() not in the initialiser of DetailView.

Load more functionality using SwiftUI

i have used ScrollView with HStack, now i need to load more data when user reached scrolling at last.
var items: [Landmark]
i have used array of items which i am appeding in HStack using ForEach
ScrollView(showsHorizontalIndicator: false) {
HStack(alignment: .top, spacing: 0) {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
}
}
}
What is the best possible solution to manage load more in SwiftUI without using custom action like loadmore button.
It's better to use ForEach and List for this purpose
struct ContentView : View {
#State var textfieldText: String = "String "
private let chunkSize = 10
#State var range: Range<Int> = 0..<1
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("Load more")
}
}
}
func loadMore() {
print("Load more...")
self.range = 0..<self.range.upperBound + self.chunkSize
}
}
In this example each time you press load more it increases range of State property. The same you can do for BindableObject.
If you want to do it automatically probably you should read about PullDownButton(I'm not sure if it works for PullUp)
UPD:
As an option you can download new items by using onAppear modifier on the last cell(it is a static button in this example)
var body: some View {
List {
ForEach(range) { number in
Text("\(self.textfieldText) \(number)")
}
Button(action: loadMore) {
Text("")
}
.onAppear {
DispatchQueue.global(qos: .background).asyncAfter(deadline: DispatchTime(uptimeNanoseconds: 10)) {
self.loadMore()
}
}
}
}
Keep in mind, that dispatch is necessary, because without it you will have an error saying "Updating table view while table view is updating). Possible you may using another async way to update the data
If you want to keep using List with Data instead of Range, you could implement the next script:
struct ContentView: View {
#State var items: [Landmark]
var body: some View {
List {
ForEach(self.items) { landmark in
CategoryItem(landmark: landmark)
.onAppear {
checkForMore(landmark)
}
}
}
}
func checkForMore(_ item: LandMark) {
guard let item = item else { return }
let thresholdIndex = items.index(items.endIndex, offsetBy: -5)
if items.firstIndex(where: { $0.id == item.id }) == thresholdIndex {
// function to request more data
getMoreLandMarks()
}
}
}
Probably you should work in a ViewModel and separate the logic from the UI.
Credits to Donny Wals: Complete example

unresolved identifier in swift 3

My code is trying to download some JSON data and save it to an array, then loop through the array and create a button for each item. I am having trouble assigning my function to the buttons for giving them functionality. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//connect to website
let SongArray: Array<Any>
let url = URL(string:"*******")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
//download JSON data from php page, display data
let SongArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [[String]]
print(SongArray)
//Make buttons with JSON array
var buttonY: CGFloat = 20
for song in SongArray {
let SongButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // 50px spacing
SongButton.layer.cornerRadius = 10 //Edge formatting for buttons
SongButton.backgroundColor = UIColor.darkGray //Color for buttons
SongButton.setTitle("\(song[0])", for: UIControlState.normal) //button title
SongButton.titleLabel?.text = "\(song[0])"
SongButton.addTarget(self,action: #selector(songButtonPressed(_:)), for: UIControlEvents.touchUpInside) //button press / response
self.view.addSubview(SongButton) // adds buttons to view
}
}
catch
{
}
}
}
}
task.resume()
}
} //close viewDidLoad
func songButtonPressed(_sender:UIButton!) { // function for buttons
if sender.titleLabel?.text == "\("Song[0]")" {
print("So far so good!!")
}
}
I am getting an error on the line with SongButton.addTarget...
the error says 'Use of Unresolved Identifier "SongButtonPressed"' even though its declared right after the viewDidLoad function.
Since you declared the selector SongButtonPressed(_:) it's (note the underscore)
func SongButtonPressed(_ sender: UIButton) { // no implicit unwrapped optional !!
By the way the proper selector syntax is #selector(SongButtonPressed(_:))
Two notes:
.mutableContainers has no effect in Swift at all. Omit the parameter. And delete the line let SongArray: Array<Any>. You should get an unused warning.
Functions, methods and variables are supposed to start with a lowercase letter.