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.
Related
In my project I am using a mapView to render locations from Lat-Lon received from an API. My project has a button which does the follow:
When clicked, it fires a timer that retrieves coordinates from the web and then plots on the mapview
When clicked again, it stops the timer and no data is retrieved.
However even when the timer is stopped it consumes a lot of memory around 100mbs if not more. So I want to release the memory when user is not using map and when they are map should come back again. I did the following to release memory:
self.mapView.delegate = nil;
self.mapView.removeFromSuperview()
self.mapView = nil;
This removes map and my memory comes back to 20mbs, normal. However is this the correct way to release memory? and how do I get it back once the button is pressed?.
To add a map you can do this:
import UIKit
import MapKit
class ViewController: UIViewController {
var mapView: MKMapView?
#IBOutlet weak var framer: UIView!//uiview to put map into
var coordinate = CLLocationCoordinate2D(){
willSet{
print("removing annotation...")
if let m = mapView{
m.removeAnnotation(anno)
}
}
didSet{
print("did set called, adding annotation...")
anno.coordinate = coordinate
if let m = mapView{
m.addAnnotation(anno)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func start(_ sender: Any) {
let mk = MKMapView()
mk.bounds = framer.bounds
mk.mapType = MKMapType.standard
mk.isZoomEnabled = true
mk.isScrollEnabled = true
// Or, if needed, we can position map in the center of the view
mk.center = framer.center
mapView = mk
if let mk2 = mapView{
framer.addSubview(mk2)
}
}
To remove
#IBAction func stop(_ sender: UIButton) {
if mapView != nil{
if let mk2 = mapView{
mk2.delegate = nil;
mk2.removeFromSuperview()
mapView = nil;
}
}
}
}
I have a tabbed app which loads separate internal file WKwebviews. I now need to have the WKwebview refresh when a tab is selected.
I think I need to add the required code in the viewWillAppear, but on trying to have some code on this method nothing works.
Does anyone have any suggestions as to how I can achieve this
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
webView.configuration.userContentController.add(self, name: "jsHandler")
let bundleURL = Bundle.main.resourceURL!.absoluteURL
let html = bundleURL.appendingPathComponent("main/index.html") //Loads internal HTML files
webView.loadFileURL(html, allowingReadAccessTo:bundleURL)
webView!.uiDelegate = self
webView.allowsBackForwardNavigationGestures = true //Allows 'safari' style gestures swipe back etc
}
override func viewWillAppear(_ animated: Bool) {
//Nothing
}
Update:
To resolve this, I added the above code into the viewDidAppear method rather than the viewDidLoad. This has resolved the problem.
The JS message handler still needs to be in the viewDidLoad.
Example:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
webView.configuration.userContentController.add(self, name: "jsHandler")
}
override func viewDidAppear(_ animated: Bool) {
//webView.configuration.userContentController.add(self, name: "jsHandler")
let bundleURL = Bundle.main.resourceURL!.absoluteURL
let html = bundleURL.appendingPathComponent("index.html") //Loads internal HTML files
webView.loadFileURL(html, allowingReadAccessTo:bundleURL)
webView!.uiDelegate = self
webView.allowsBackForwardNavigationGestures = true //Allows 'safari' style gestures swipe back etc
}
Try something like this on each tab action:
func reloadTabAction() {
if let url = webView.url {
webView.reload()
} else {
webView.load(URLRequest(url: originalURL))
}
}
We have reload property for WKWebView. So you can directly call the method.
You can call the method while tapped the tabView
Try the below code,
webView.reload()
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 having a issue in Unwinding/Closing a View Controller.
My navigation bar in this view controller has two buttons. The left one is an "Unwind/exit" button that functions fine and closes the current view controller and returns the user back to the main setting screen. The second button is a "Save" button that commits the users data to the NSUserdefaults. Both buttons do what they are designed. Although I would like the "Save" button to do one more thing.
I would like the Navigation Bar "Save" button to continue to save as data as its designed, but I would also like it to "Unwind/Exit" that view controller and return to the main Settings view controller just like the "Unwind/Exit" navigation bar button does.
Here is my code for the view controller. As you will be able to see, i am calling the "backButtonTapped" function from within my "settingSaveEmailAddress" function. I know the "backButtonTapped" function is being triggered because the "print" command is printing in the debug window. Although it is saving my data, it is failing to close the view controller and unwinding back to the main settings view controller.
import UIKit
class SettingsViewController: UIViewController {
#IBOutlet weak var settingEmailAddress: UITextField!
#IBOutlet weak var settingEmailAddressLastUpdate: UILabel!
// Constant keys for NSUserDefaults look ups
static let settingDefaultEmailAddress = "EMAIL_ADDRESS"
static let settingDefaultEmailAddressLastUpdated = "EMAIL_ADDRESS_LAST_UPDATED"
//End Setting - Email Address
#IBAction func settingSaveEmailAddress(sender: AnyObject) {
if (settingEmailAddress.text!.characters.count > 0) {
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setObject(settingEmailAddress.text, forKey: SettingsViewController.settingDefaultEmailAddress)
saveTimestampEmailAddress()
}
dismissKeyboard()
print(settingEmailAddress.text)
backButtonTapped(self)
}
//End Setting - Email Address
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment these if you want to clear out the saved defaults
//let appDomain = NSBundle.mainBundle().bundleIdentifier!
//NSUserDefaults.standardUserDefaults().removePersistentDomainForName(appDomain)
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(SettingsViewController.dismissKeyboard))
view.addGestureRecognizer(tap)
// update labels from NSUserDefaults
getUserPreferences()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Updates the view with the user values already stored in NSUserDefaults
func getUserPreferences() {
let prefs = NSUserDefaults.standardUserDefaults()
// Get Email Address
if let email = prefs.stringForKey(SettingsViewController.settingDefaultEmailAddress) {
settingEmailAddress.text = email
}
// Get the last time something was stored
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.LongStyle
dateFormatter.timeStyle = .MediumStyle
if let lastUpdateStored = (prefs.objectForKey(SettingsViewController.settingDefaultEmailAddressLastUpdated) as? NSDate) {
settingEmailAddressLastUpdate.text = "Last Update:" + dateFormatter.stringFromDate(lastUpdateStored)
} else {
settingEmailAddressLastUpdate.text = "Last Update: Never"
}
}
// MARK: - Keyboard responders so the keyboard goes away when we're done editing.
// Dismiss the keyboard when the user is finished editing.
func dismissKeyboard(){
// Resign the first responder status.
view.endEditing(true)
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
// Saves the timestamp of when the user has made a change to the NSUserDefaults
func saveTimestampEmailAddress() {
let prefs = NSUserDefaults.standardUserDefaults()
let timestamp = NSDate()
prefs.setObject(timestamp, forKey: SettingsViewController.settingDefaultEmailAddressLastUpdated)
let dateFormatter = NSDateFormatter()
dateFormatter.dateStyle = NSDateFormatterStyle.LongStyle
dateFormatter.timeStyle = .MediumStyle
settingEmailAddressLastUpdate.text = "Last Update:" + dateFormatter.stringFromDate(timestamp)
}
func backButtonTapped(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
print("Exit Triggered")
}
}
You can write :-
In Objective-C
[self.navigationController popToRootViewControllerAnimated:YES];
In Swift
func popToRootViewControllerAnimated(_ animated: Bool) -> [UIViewController]?
at the end of your "Save" buttons method if your "Main Setting" viewController is the root viewController.
I created this function and I am now getting the expected functionality when I press "Save".
Save NSUserDefault data
Close current ViewController and return to
my main settings view controller "SettingsMainTableViewController".
func closeVCReturnSettings() {
for controller in self.navigationController!.viewControllers as Array {
if controller.isKindOfClass(SettingsMainTableViewController) {
self.navigationController?.popToViewController(controller as UIViewController, animated: true)
break
}
print("Return to settings")
}
}
radarMap is a UIWebView object and exitMapButton is its close button. To access map I used hidden actions. Now I want to add fade out and fade in animations while hiding. I did fade in but not fade out. How can I add fade out animation while hiding?
func openRadarMap(){
radarMap.hidden = false
exitMapButton.hidden = false
self.radarMap.alpha = 0
self.exitMapButton.alpha = 0
}
override func viewDidAppear(animated: Bool) {
if radarMap.hidden == false {
super.viewDidAppear(animated)
UIView.animateWithDuration(0.5, animations: {
self.radarMap.alpha = 1.0
self.exitMapButton.alpha = 1.0
}) }
}
func exitFromMap() {
exitMapButton.hidden = true
radarMap.hidden = true
self.exitMapButton.alpha = 0.0
self.radarMap.alpha = 0.0
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
UIView.animateWithDuration(0.5, animations: {
self.radarMap.alpha = 0.0
self.exitMapButton.alpha = 0.0
})
}
#IBAction func exitMapButtonAction(sender: AnyObject) {
exitFromMap()
}
#IBAction func webView(sender: UIButton) {
getAd()
openRadarMap()
let URL = "somewebpage.com/map"
let requestURL = NSURL(string:URL)
let request = NSURLRequest(URL: requestURL!)
radarMap.loadRequest(request)
//performSegueWithIdentifier("mapView", sender: nil)
}
The method viewDidAppear will be called after the view was removed from the view hierarchy. the description of the method says ,
Notifies the view controller that its view was removed from a view hierarchy.
So the view will not be actually visible at that time, I suggest you to write the fade out code in viewWillDisappear
You need to call super in all cases. If you don't call super for viewDidAppear: you can't have viewDidDisappear: ever called.