iOS 10, QLPreviewViewController is not displaying files - ios

I am working on a iOS app that displays lots of different files to the user. From video and audio files to html and Office/iWorks files. I have subclassed QLPreviewController, and want to use it to display some of these files. I create the object, pass it the file's url, set the ViewController's view's frame to replace the webView's frame in the parent viewcontroller. :
else if (QuickLookViewController.canOpenFile(previewItem: contentUrl as NSURL)) {
hideControls()
quickLook.contentURLs = [contentUrl as NSURL]
//add the QuickLookController's view to content display using webview's frame
self.view.addSubview(quickLook.view)
quickLook.view.frame = webview!.frame
quickLook.reloadData()
Using the view debug hierarchy tool, it shows the view set correctly, as well as setting quickLook.backgroundColor = UIColor.black just to test.
So I thought that maybe there was a problem with accessing the download and stored file, so in the viewDidLoad of QuickLookController I added some tests to ensure that the file is there:
super.viewDidLoad()
print("\nContent URL: \(contentURLs[0])")
print("\nContent URL Path: \(contentURLs[0].path!)")
self.delegate = self
self.dataSource = self
var error : NSError?
print("\nis reachable: \(contentURLs[0].checkResourceIsReachableAndReturnError(&error))")
if (error != nil) {
print("is reachable error -> \(error.debugDescription)")
}
print("\ndoes exist: \(FileManager.default.fileExists(atPath: contentURLs[0].path!))")
print("\nCan open: \(QuickLookViewController.canOpenFile(previewItem: contentURLs[0]))")
and the log statements come out as I expect:
Content URL: file:///var/mobile/Containers/Data/Application/B9D5C288-F889-4513-941E-2564F1C12F02/Documents/588c5a1e-dffe-47a8-9824-bc19463aafc2/d88a8dd5-40d1-4fdb-adf3-10fce1f6bf1f/fd73c162-5ac3-4269-8573-9c0b61bef7a7/fd73c162-5ac3-4269-8573-9c0b61bef7a7.pages
Content URL Path: /var/mobile/Containers/Data/Application/B9D5C288-F889-4513-941E-2564F1C12F02/Documents/588c5a1e-dffe-47a8-9824-bc19463aafc2/d88a8dd5-40d1-4fdb-adf3-10fce1f6bf1f/fd73c162-5ac3-4269-8573-9c0b61bef7a7/fd73c162-5ac3-4269-8573-9c0b61bef7a7.pages
is reachable: true
does exist: true
Can open: true
I even used a breakpoint in the the viewDidLoad to check that the quickLook's superview is set using 'po self.view.superview!.frame' as a llbd statement, and again received the output I expected.
I used this same class for another view stack in the app and it displays the files that are clicked, so it isn't making much sense to me. The only difference between the two uses, the second I am presenting the quickLook's view in a viewController that is presented modally.

ok I wanted to post the answer to the problem I was having. The issue was I was not adding the quickLook view controller to the ContentDisplayViewController with a parent-child relationship. So the updated code to add the view I wanted looks like:
//create the parent-child relationship for the view controllers
self.addChildViewController(quickLook)
//give the view it's frame
quickLook.view.frame = webview!.frame
//add the view and call the child's didMove() method.
self.view.addSubview(quickLook.view)
quickLook.didMove(toParentViewController: self)
This question gave me the answer:
Add child view controller to current view controller
Gave me me some reading material to read into, thanks for the post #Piyush Patel

Related

WKWebView in iOS app takes a long time to load

I have a UIView that has a WKWebView as a subview. The web view shows up all right, but it takes a long time (more than a second) to display the HTML document. The document is a local file (an HTML file in the project) so there is no Internet latency, and it's a relatively simple HTML document. The HTML document does have eight small images on it, but there is a similar problem with another HTML document that doesn't have any images.
Here's the code that loads the HTML document into the web view:
override func viewDidLoad() {
super.viewDidLoad()
let localHtmlFile = Bundle.main.url(forResource: "place", withExtension: "html");
let request = URLRequest(url: localHtmlFile!);
webView.load(request);
}
I am using Xcode 9.3, Swift 4.1, and iOS 11.2.
The delay happens every time I go to that screen. If it isn't possible to prevent the delay the first time, is it possible to keep the web view around so that the delay only happens once?
Apparently the delay is caused by the time it takes to make a new instance of WKWebView, not the time it takes to load an HTML document. To avoid that delay I figured out a way to reuse a web view.
First I removed the web view from the storyboard scene so that a new web vew wouldn't be created every time the view was loaded. I made a generic view named container that is the same size that I wanted the web view to be.
Then I made a static variable to keep a pointer to the web view:
static var webView: WKWebView? = nil
In my case this static variable is in a class called GameController.
Next I changed the code to check to see if the static webView variable is nil. If webView is nil, the code creates a new web view and sets the static variable to point to that web view. Then the code programmatically adds the web view as a subview of a container view in the storyboard scene.
To set up the storyboard and write this code I used the explanation on the following web site:
http://www.onebigfunction.com/ios/2016/12/14/iOS-javascript-communication/
The basic code in the view controller for the scene that uses the web view (WebViewController in my code) looks like this:
override func loadView() {
super.loadView()
if GameController.webView == nil {
var webFrame = self.container!.frame
webFrame.origin.x = 0
webFrame.origin.y = 0
let config = WKWebViewConfiguration()
webView = WKWebView(frame: webFrame,
configuration: config)
GameController.webView = webView
} else {
webView = GameController.webView
}
self.container!.addSubview(webView)
}
In my case, I wanted to send information from JavaScript code in the web view to Swift code in my app, so I had to work more with configurations. I also wanted the web view to be transparent, so I added a statement to do that.
override func loadView() {
super.loadView()
if GameController.webView == nil {
var webFrame = self.container!.frame
webFrame.origin.x = 0
webFrame.origin.y = 0
let config = WKWebViewConfiguration()
config.userContentController.add(self, name: "scriptHandler")
webView = WKWebView(frame: webFrame,
configuration: config)
webView.isOpaque = false
GameController.webView = webView
} else {
webView = GameController.webView
webView.configuration.userContentController.removeScriptMessageHandler(
forName: "scriptHandler")
webView.configuration.userContentController.add(self,
name: "scriptHandler")
}
self.container!.addSubview(webView)
}
Originally I only set the script handler when I first made the web view, but that didn't work. Apparently a new view controller object was made each time the scene was loaded, so the old script handler didn't work. This code deletes the script handler that pointed to the old view controller and adds a script handler that points to the new view controller.
Just keep a reference to the ViewController, perhaps in a parent controller, app delegate, or even a singleton/global. It should be somewhat faster on subsequent loads.

Is it fine to nest a UIViewController within another without using addChildViewController?

I'm trying to make a custom ContainerViewController, but due to lots of difficulties with the ViewController transitions and making everything interactive, I've decided to mimic that functionality myself.
What I basically want to do, is have a paginated UIScrollView (the HeaderView) on the top control different another UIScrollView (the ControllersView) below that contains ViewControllers as pages so that as you swipe to a new page on the HeaderView, it also swipes to the next viewcontroller on the ControllersView. This is what the setup would look like.
My question is, is there anything wrong with having the aforementioned setup? All I'll do to add the view controllers to the ControllersView is just something like: controllersView.addSubview(pagecontroller1.view).
Some posts online seem to say that "the appropriate ViewController functions won't be called" or whatever. What do I seem to be missing here? I'm guessing there's a lot of dismissing and admitting of ViewControllers that I need to call every time a ViewController is out of frame right?
To clarify the question: Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call? I'm realizing that if I were to set things up this way, I need to manage a lot of things that are usually handled automatically, but as I mentioned before, custom ContainerViewControllers have failed me and I'm going with this.
PS. If you seem to still be lost on how this will look like, see my previous question here where I originally wanted to use a Container ViewController. There's a much better mockup there.
You can add and remove VC In Container Views
For - Is it ok/efficient to do this? Should I be calling some viewWillAppear/disapper functions when the VC's get in and out of frame? If so, what should I call?
As, We need to call WillAppear and Disappear Func when Adding and removing a VC , Thus Try using below Functions That will Handle these Responses
I use the Two specific Functions to add and remove Controller in ContainerView/UIView/SubView in ScrollView inside a UIView
To Add
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
To Remove
private func remove(asChildViewController viewController: UIViewController)
{
// Notify Child View Controller
viewController.willMove(toParentViewController: nil)
secondContainer.willRemoveSubview(viewController.view)
// Remove Child View From Superview
viewController.view.removeFromSuperview()
// Notify Child View Controller
viewController.removeFromParentViewController()
}
Creating Object
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
For - controllersView.addSubview(pagecontroller1.view)
Answer - Yes Approbate func wont be called if pagecontroller1 is not loaded in to memory stack, to load that you need to notify pagecontroller1 that it is going to be added to memory stack as Child View , Just as We initiate a Controller and basically notifies the Controller to get its component loaded to memory stack to get some memory allocations
For Question - Is it fine to nest a UIViewController within another without using addChildViewController?
Check apple Documentation - https://developer.apple.com/documentation/uikit/uiviewcontroller/1621394-addchildviewcontroller
This is necessary just as to notify the controller who is going to be added in Another Parent View as Child
Sample Project
https://github.com/RockinGarg/Container_Views.git
Or
https://github.com/RockinGarg/ContainerView-TabBar.git
If Question is Still not answered Please Tell me what Func Exactly you want to handle by yourself

ViewDidLoad is always called

I just recently started to use Swift and am facing a "weird" bug with the viewDidLoad method.
My very simple app currently only has 2 viewcontrollers:
MainViewController, which is the Apps entry point and serves as an overview for the data which has already been created. It also provides the option to add new data, which would trigger a segue to
DataViewController, which provides the UI to create new data, after which it goes back to the MainViewController
Now my issue is that the viewDidLoad method of MainViewController is always called whenever the MainViewController appears (At the start of the app and every time the DataViewController disappears). Particularly, the msg "MainViewController newly created" is always printed.
Even worse, it seems that my app is "secretly" resetting. To show this I have defined the class variable "createView" in my MainViewController which is true by default, and is set to false during the viewDidLoad (the only place where this variable is called/set). However the msg "MVC newly created" is still always printed in the output after the MainViewController shows up. How can that be? Why / how is createView reset to true?
Hope this snippet is enough to find the issue. Otherwise, let me know if something is missing.
Thanks for your help!
override func viewDidLoad()
{
super.viewDidLoad()
if (createView)
{
determineArraySize()
createDataArray()
print("MainViewController newly created")
createView = false
}
else {print("Nothing happened")}
}
As #moritz mentioned in the comments, check out the way you present the DataViewController in your storyboard.
If the view is presented modally, you should call:
dismiss(animated: true, completion: nil)
If the view is presented using a show seque, you should call:
_ = navigationController?.popViewControllerAnimated(true)

View not loading from Storyboard on InstantiateViewController() call

I am trying to present a view controller in a Forms dependency service. But I keep getting a black screen. I can get it to work when I use Swift in Xcode, but not using Xamarin.
Controller outlets are not loading either. Strange thing is changes to vc.View do appear. It’s as though it’s creating a blank view and not actually loading from the storyboard.
My code is as follows:
var storyboard = UIStoryboard.FromName("StoryboardView", null);
var controller = storyboard.InstantiateViewController("StoryboardViewVC");
var window = UIApplication.SharedApplication.KeyWindow;
var vc = window.RootViewController;
while (vc.PresentedViewController != null)
{
vc = vc.PresentedViewController;
}
vc.PresentViewController(controller, true, null);
Adding a call to LoadViewIfNeeded() does not fix the issue.
I have double-checked that the storyboard and controller names match, which they do. I’ve searched for answers, but everything I’m finding revolves around not writing code to load the controller from the storyboard or nib.
var controller = (UIViewController)storyboard.InstantiateViewController("StoryboardViewVC");
use class you assigned to your view that may load your view.

iOS - Programatically load view on app didFinishingLaunching

This question is mostly about how I should structure my app. When the app loads the user can select from a list of videos to play, however the actual video player is on a different view controller. At the moment I am just keeping that view controller in memory so that the video can play continuously while the user is navigating throughout the app.
So the problems is that if the user selects a video before loading that movie view controller, nothing will happen of course.
How should I structure my app so that the video can play continuously whether or not the movie player view controller is held in memory? Is this possible?
I'm not 100% sure what you're trying to do; but a default project created in XCode will "Programatically load view on app didFinishingLaunching" without any further coding needed.
Generally you should structure your app with an initial view or view controller (like a navigation controller); and start any visible actions in the "viewDidLoad" method of the target view.
In your case, I would recommend loading straight to the video controller first; and then programmatically segueing to the video selector view if no video is selected.
Usually, I create a structure like this in my projects, I think you need to take a special look in OverlayViewController, check out a example:
MainViewController - As rootViewController
|- content (Any view controller to be presented, yes inside MainViewController)
|- OverlayViewController (a view controller over content )
This structure allows you to change contents without change entire hierarchy.
At MainViewController you will need a method to change current content to another, there is a simple example of how can you do that:
// PS I'm not sure if this will work in the first try, I wrote from my mind right now :)
func changeViewController (controller: UIViewController) {
let from: UIViewController
if childViewControllers.count > 0 {
from = childViewControllers.first as! UIViewController
}
else {
presentViewController(controller)
return
}
let transitionContext = SomeViewControllerContextTransitioning (
fromViewController: from,
toViewController: controller
)
transitionContext.onAnimationComplete = { success in
if !success {
// TODO: Error fallback
}
else {
from.view.removeFromSuperview()
from.removeFromParentViewController()
controller.didMoveToParentViewController(self)
}
}
from.willMoveToParentViewController(nil)
addChildViewController(controller)
SomeAnimator().animateTransition(transitionContext)
}
And finally, in your MainViewController you can observe or control your video playback.
I hope this can help you.

Resources