I have implemented a simple webview in ios with a PDF being displayed, and I want to do 2 things:
1- remove the gray padding area around the pdf doc when it's shown on webview
2-display a button in a specific location over the pdf file given the coordinates of the button
For both the two issues above, I have found code on SO and changed them to my needs. Here is the code I have so far (the view controller)
class DocAreaController: UIViewController, UIWebViewDelegate{
#IBOutlet var myWebView: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
myWebView.scrollView.scrollEnabled = false
myWebView.scrollView.bounces = false
myWebView.scrollView.pagingEnabled = true
myWebView.userInteractionEnabled = true
myWebView.delegate = self
func webViewDidStartLoad(myWebView: UIWebView){
let paddingScript = "document.body.style.margin='0';document.body.style.padding = '0'"
let result = myWebView.stringByEvaluatingJavaScriptFromString(paddingScript)
debugPrint("method called")
let btn: UIButton = UIButton(frame: CGRectMake(188, 340, 46, 30))
btn.backgroundColor = UIColor.cyanColor() //.colorWithAlphaComponent(0.5)
btn.userInteractionEnabled = false
btn.tag = 1 // change tag property
myWebView.addSubview(btn) // add to view as subview
}
// Get the document's file path.
let filepath = (NSBundle.mainBundle().pathForResource("Reader", ofType: "pdf"))! as String
// Create an NSURL object based on the file path.
let url = NSURL.fileURLWithPath(filepath)
// Create an NSURLRequest object.
let request = NSURLRequest(URL: url)
// Load the web viewer using the request object.
myWebView.loadRequest(request)
}
}
But the webViewDidStartLoad method is not getting called. I have set the delegate for webview both in code and storyboard. What m I missing?
Shift the webViewDidStartLoad out of viewDidLoad method. Place it after the viewDidLoad and it should work.
override func viewDidLoad() {
super.viewDidLoad()
myWebView.scrollView.scrollEnabled = false
myWebView.scrollView.bounces = false
myWebView.scrollView.pagingEnabled = true
myWebView.userInteractionEnabled = true
myWebView.delegate = self
// Get the document's file path.
let filepath = (NSBundle.mainBundle().pathForResource("Reader", ofType: "pdf"))! as String
// Create an NSURL object based on the file path.
let url = NSURL.fileURLWithPath(filepath)
// Create an NSURLRequest object.
let request = NSURLRequest(URL: url)
// Load the web viewer using the request object.
myWebView.loadRequest(request)
}
func webViewDidStartLoad(myWebView: UIWebView){
let paddingScript = "document.body.style.margin='0';document.body.style.padding = '0'"
let result = myWebView.stringByEvaluatingJavaScriptFromString(paddingScript)
debugPrint("method called")
let btn: UIButton = UIButton(frame: CGRectMake(188, 340, 46, 30))
btn.backgroundColor = UIColor.cyanColor() //.colorWithAlphaComponent(0.5)
btn.userInteractionEnabled = false
btn.tag = 1 // change tag property
myWebView.addSubview(btn) // add to view as subview
}
Related
I'm trying to display a pdf on ios via apples PDFKit library, and rather than use PDFDisplayMode.singlePageContinuous mode, I want to stop at page breaks so I'm trying to use PDFDisplayMode.singlePage.
https://developer.apple.com/documentation/pdfkit/pdfdisplaymode
However, this mode seems to only display one page of the pdf which is quite useless. I've tried adding swipe handlers to the page but they aren't working either.
I've found sample applications and altered their code to test the pdfdisplaymode but get the same problem e.g.
https://github.com/vipulshah2010/PDFKitDemo
How can I implement a one page at a time pdfviewer with pdfkit, that allows swiping between pages?!
A another simple way to do this is setting
pdfView.usePageViewController(true)
This adds the swiping between pages for you and no need to set up your own gestures. See example below:
override func viewDidLoad() {
super.viewDidLoad()
// Add PDFView to view controller.
let pdfView = PDFView(frame: self.view.bounds)
self.view.addSubview(pdfView)
// Configure PDFView to be one page at a time swiping horizontally
pdfView.autoScales = true
pdfView.displayMode = .singlePage
pdfView.displayDirection = .horizontal
pdfView.usePageViewController(true)
// load PDF
let webUrl: URL! = URL(string: url)
pdfView.document = PDFDocument(url: webUrl!)
}
Use the swipe gesture recognizer (UISwipeGestureRecognizer) to let the user swipe the PDF view screen (PDFView) to the left and right.
import UIKit
import PDFKit
class ViewController: UIViewController, PDFViewDelegate {
// MARK: - Variables
// MARK: - IBOutlet
#IBOutlet weak var pdfView: PDFView!
// MARK: - Life cycle
override func viewDidLoad() {
super.viewDidLoad()
let filePath = "/Users/george/Library/Developer/CoreSimulator/Devices/B5C5791C-3916-4BCB-8EB6-5D3D61C08DC0/data/Containers/Data/Application/4B644584-0025-45A7-9D71-C8F8478E4620/Documents/my PDF.pdf"
pdfView.document = getDocument(path: filePath)
pdfView.backgroundColor = .lightGray
pdfView.autoScales = true
pdfView.displayMode = .singlePageContinuous
pdfView.usePageViewController(true, withViewOptions: nil)
createMenu()
thumbnail()
/* swipe gesture */
let leftSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondLeftSwipeGesture(_:)))
leftSwipeGesture.direction = [UISwipeGestureRecognizer.Direction.left]
self.view.addGestureRecognizer(leftSwipeGesture)
let rightSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(respondRightSwipeGesture(_:)))
rightSwipeGesture.direction = [UISwipeGestureRecognizer.Direction.right]
pdfView.addGestureRecognizer(rightSwipeGesture)
}
#objc func respondLeftSwipeGesture(_ sender: UISwipeGestureRecognizer) {
if pdfView.document == nil { return }
pdfView.goToPreviousPage(self)
}
#objc func respondRightSwipeGesture(_ sender: UISwipeGestureRecognizer) {
if pdfView.document == nil { return }
pdfView.goToNextPage(self)
}
func getDocument(path: String) -> PDFDocument? {
let pdfURL = URL(fileURLWithPath: path)
let document = PDFDocument(url: pdfURL)
return document
}
}
You might simply set the displayMode to continuous and it might work:
pdfView.displayMode = .singlePageContinuous
I load the progress loading animation and when the response from Alamofire comes I use part of the response to construct the full url I need to load in the wkwebview and then I trigger webview.load(..).
My problem is that the progress loading animation gets stuck as soon as webview.load(..) starts to happen and remain stuck till I hide() it.
How can I actually have my animation to keep moving meanwhile the webview starts loading the page?
MyViewController.swift
class MyViewController: UIViewController, WKScriptMessageHandler {
var webView: WKWebView?
#IBOutlet weak var webViewContainer: UIView!
var webConfig:WKWebViewConfiguration {
get {
let webCfg:WKWebViewConfiguration = WKWebViewConfiguration()
let userController:WKUserContentController = WKUserContentController()
userController.add(self, name: "mycontroller")
webCfg.userContentController = userController;
return webCfg;
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView (frame: webViewContainer.bounds, configuration: webConfig)
webView!.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webView?.scrollView.isScrollEnabled = false
webViewContainer.addSubview(webView!)
loadWebview()
}
func loadWebview(){
Loading.shared.show(self.view)
Alamofire.request(MYAPI, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: nil)
.responseJSON { response in
let url = URL(string: "https://path-to-load/\(response.key)")
self.webView!.load(URLRequest(url: url!))
}
}
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
let message = message.body as! [String:AnyObject]
let event = message["event"] as? String ?? "empty"
switch (event){
case "loading-finished":
DispatchQueue.main.async {
Loading.shared.hide(animated: true)
}
break
default:
break
}
}
}
Loading.swift
public class Loading {
var blurredEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
let imageView = UIImageView(image: UIImage(named: "loading_image"))
class var shared: Loading {
struct LoadingStatic {
static let instance: Loading = Loading()
}
return LoadingStatic.instance
}
init() {
imageView.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
imageView.contentMode = .scaleToFill
blurredEffectView.contentView.addSubview(imageView)
}
public func show(_ view: UIView, inView: Bool = false) {
var window: UIView!
if inView == false, let w = view.window {
window = w
} else {
window = view
}
if blurredEffectView.superview == window {
return
}
let rotation: CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
imageView.layer.add(rotation, forKey: "rotationAnimation")
imageView.center = window.center
blurredEffectView.frame = window.bounds
window.addSubview(blurredEffectView)
blurredEffectView.fadeIn()
}
}
Possible solutions:
1) Make the loading happens in Application Window when you want a full screen Loading (inview == false by default) and keep loadWebview() in viewDidLoad
public func show(_ view: UIView? = nil, inView: Bool = false) {
var window: UIView!
if inView == false, let w = UIApplication.shared.delegate?.window {
window = w
} else {
window = view
}
...
2) Move loadWebview() from viewDidLoad to viewDidAppear
The important part that is moving here is Loading.shared.show(self.view). I wasn't able to animate the components of my view until it has finished laying out (which happens exactly in viewDidAppear()).
More details: viewDidLoad is called after the MyViewController has been initialised and has initialised the main view but before the view is presented to the UI. This means that viewDidLoad allows me to setup the ViewController and the View, before it is shown to the user for interactions.
Although it was a nice and quick solution, in some situation this may not work as expected as viewDidAppear can be called twice, and hence showing the loading view twice which will result in a weird ux.
I'm loading html string by WebView in my app. Current issue is that when my WebView load content that contains another link and when user click on that link, new view is not opening in my app - it opens in default browser. How can I open that link in WebView? Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
self.edgesForExtendedLayout = UIRectEdge.None
SVProgressHUD.show()
//EZLoadingActivity.show("أرجو الإنتظار", disableUI: true)
self.automaticallyAdjustsScrollViewInsets = false
self.navigationController!.navigationBar.tintColor = UIColor.whiteColor()
self.navigationController!.navigationBar.barStyle = UIBarStyle.Black
self.navigationController!.navigationBar.tintColor = UIColor.whiteColor()
let logo = UIImage(named: "toolbar_icon3")
let imageView = UIImageView(image:logo)
self.navigationItem.titleView = imageView
myweb.loadHTMLString(webviewurl, baseURL: nil)
}
Android allows to do it with:
public boolean shouldOverrideUrlLoading(WebView view, String url)
But on iOS how can we do this?
You could try creating a url request for you UIWebView.
yourWebView.loadRequest(NSURLRequest(URL: NSURL(string: "google.ca")!))
If you would like to do something while the web view is opening, you could use the UIWebViewDelegate statement.
In your class:
class ViewController: UIViewController, UIWebViewDelegate {
Then,
yourWebView.delegate = self
And finally, you can use:
func webViewDidStartLoad(webView: UIWebView) {
//do something while the web view is loading!
}
For more information on UIWebViewDelegate, visit Apple's Documentation.
I created a UIViewController having a view with two UIWebViews. My structure looks like this (I also use auto layout with SnapKit):
class MyIntViewController: UIViewController, UIWebViewDelegate {
private var scrollView: UIScrollView!
private var contentView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.whiteColor()
setupView()
}
private func setupView() {
scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.view)
}
contentView = UIView()
contentView.backgroundColor = UIColor.greenColor()
scrollView.addSubview(contentView)
contentView.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(self.scrollView)
make.width.equalTo(self.view.bounds.width)
}
// first webView
let contentWebView1 = UIWebView()
contentWebView1.scalesPageToFit = false
contentWebView1.delegate = self
contentWebView1.scrollView.bounces = false
contentWebView1.scrollView.scrollEnabled = false
contentView.addSubview(contentWebView1)
contentWebView1.snp_makeConstraints { (make) -> Void in
make.top.equalTo(contentView.snp_top)
make.left.equalTo(self.view.snp_left)
make.right.equalTo(self.view.snp_right)
make.height.equalTo(1)
}
// second webView
let contentWebView2 = UIWebView()
contentWebView2.scalesPageToFit = false
contentWebView2.delegate = self
contentWebView2.scrollView.bounces = false
contentWebView2.scrollView.scrollEnabled = false
contentView.addSubview(contentWebView2)
contentWebView2.snp_makeConstraints { (make) -> Void in
make.top.equalTo(contentWebView1.snp_bottom)
make.left.equalTo(self.view.snp_left)
make.right.equalTo(self.view.snp_right)
make.height.equalTo(1)
make.bottom.equalTo(self.contentView.snp_bottom)
}
// load content for contentWebView1 and contentWebView2
let path: String = NSBundle.mainBundle().bundlePath
let baseURL: NSURL = NSURL.fileURLWithPath(path)!
let myFileHtml: String = "HTWML for contentWebView1""
let myFileHtmlPlusCss = MMCssHelper.appendCustomCSS(myFileHtml)
contentWebView1.loadHTMLString(myFileHtmlPlusCss, baseURL: baseURL)
let path: String = NSBundle.mainBundle().bundlePath
let baseURL: NSURL = NSURL.fileURLWithPath(path)!
let myFileHtml: String = "HTWML for contentWebView2""
let myFileHtmlPlusCss = MMCssHelper.appendCustomCSS(myFileHtml)
contentWebView2.loadHTMLString(myFileHtmlPlusCss, baseURL: baseURL)
}
// MARK: UIWebViewDelegate
func webViewDidFinishLoad(webView: UIWebView) {
let webViewContentHeight: CGFloat = webView.scrollView.contentSize.height
println("webViewDidFinishLoad webViewContentHeight: \(webViewContentHeight)")
webView.snp_updateConstraints { (make) -> Void in
make.height.equalTo(webViewContentHeight)
}
}
}
When I have only one UIWebView on the page everything works fine the content of the UIWebView is calculated correctly in webViewDidFinishLoad. The problem occurs with two UIViewViews as shown above. The console output is this:
webViewDidFinishLoad webViewContentHeight: 1.0
webViewDidFinishLoad webViewContentHeight: 17.0
You can see that one webViewContentHeight is calculated correct while the other seems to be not calculated at all.
How do I get the height of a UIWebView calculated correctly when there are more than one UIWebViews on the page?
I'm have a WebView:
func loadPage() {
let request = NSURLRequest (URL: url!)
myBrowser.loadRequest(request)
}
Which load RTF document from this URL:
var url: NSURL = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Chapter1", ofType: "rtf")!)!
How can I save last scroll position to NSUserDefaults, to get it then (if user pause reading and resume it).
Or maybe other type or method to make bookmark in this case.
Note: Ive been trying this:
var myBookmark: CGPoint = CGPointMake(0, 0)
override func viewWillDisappear(animated: Bool) {
myBookmark = myBrowser.scrollView.contentOffset
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
myBrowser.scrollView.delegate = self
myBrowser.scrollView.setContentOffset(myBookmark, animated: false)
}
But it's not take any effect.
UPD1: translate my VAR name.
To save and retrieve your scrollview offset from NSUserDefaults, set your UIWebView's delegate and try this:
var viewLaidoutSubviews = false // <-- variable to prevent the viewDidLayoutSubviews code from happening more than once
// Save your content offset when the view disappears
override func viewWillDisappear(animated: Bool) {
var userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setValue(NSStringFromCGPoint(myBrowser.scrollView.contentOffset), forKey: "scroll_offset")
userDefaults.synchronize()
viewLaidoutSubviews = false
}
// Retrieve and set your content offset when the view re-appears
// and its subviews are first laid out
override func viewDidLayoutSubviews() {
if (!viewLaidoutSubviews) {
// If a scroll_offset is store in NSUserDefaults, use it
var userDefaults = NSUserDefaults.standardUserDefaults()
var scrollOffset:CGPoint? = CGPointFromString(userDefaults.valueForKey("scroll_offset") as? NSString)
if let position:CGPoint = scrollOffset {
myBrowser.scrollView.delegate = self
myBrowser.scrollView.setContentOffset(position, animated: false)
}
viewLaidoutSubviews = true
}
}
And utilize you UIWebView's webViewDidFinishLoad delegate method to update the scroll offset in the case that the web view didn't finish rendering before the view laid out its subviews:
// Retrieve and set your content offset once the web view has
// finished rendering (in case it hasn't finished rendering
// by the time the view's subviews were laid out)
func webViewDidFinishLoad(webView: UIWebView) {
// If a scroll_offset is store in NSUserDefaults, use it
var userDefaults = NSUserDefaults.standardUserDefaults()
var scrollOffset:CGPoint? = CGPointFromString(userDefaults.valueForKey("scroll_offset") as? NSString)
if let position:CGPoint = scrollOffset {
myBrowser.scrollView.delegate = self
myBrowser.scrollView.setContentOffset(position, animated: true)
}
}
But note, your NSUserDefaults value will also persist between app launches unless you delete it.