I have UIWebView in UITableViewCell (url for ex.: http://advisa.work/bank_partner/webview01.html , it has resizable content height).
So I observe it contentSize
func startObservingHeight() {
let options = NSKeyValueObservingOptions([.new])
webView.addObserver(self, forKeyPath: "scrollView.contentSize", options: options, context: nil)
}
So code below is called only when contentSize of webView is growing. So I increase my tableViewCell, and when I shrink some parts of html document (css changes), function below not calling, and I cannot resize cell.
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let zeChange = change as? [NSKeyValueChangeKey: NSValue] {
let newSize = zeChange[NSKeyValueChangeKey.newKey]?.cgSizeValue
changeHeight?(newSize.height)
}
}
this my callback variable:
var changeHeight: ((CGFloat?) -> ())?
Related
I try to observe a property in a view controller, for example, isBeingDimissed property.. But It doesnt seem to work... Only when I set the options to .initial do I see that it works.
But I wanna observe new values.. Am I missing anything?
class ViewController: UIViewController {
var firstViewController = FirstViewController.init()
override func viewDidLoad() {
super.viewDidLoad()
firstViewController.addObserver(self, forKeyPath: #keyPath(UIViewController.isBeingPresented), options: .new, context: &myContext)
}
#IBAction func tapButton(_ sender: Any) {
present(firstViewController, animated: true, completion: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("let me see the change:", keyPath)
}
}
Is there a way to load a WKWebView in the background/off-screen so I can hide the loading behind another view?
You can add the WKWebView to the view hierarchy but its x is your current width, so it lays out of the screen but within the rendering hierarchy.
Add WKNavigationDelegate to your class and add it to the webView like
webView.navigationDelegate = self
Then implement the following function:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation)
This function and some others are called when the webview finished loading the content, BUT this does not include the 100% finished rendering. After that there is still some time required for rendering, the time consumption for this depends on the content that was loaded.
There currently is no callback for the 100% finished loading and rendering, so if you know the files, you can calculate or use a fix delay before moving the webView into the visible rect.
OR
If you feel fine with that, you observe private values in the webview and move your webview after those value changes to your preferred state. This looks for example like that:
class MyCustomWKWebView: WKWebView {
func setupWebView(link: String) {
let url = NSURL(string: link)
let request = NSURLRequest(url: url! as URL)
load(request as URLRequest)
addObserver(self, forKeyPath: "loading", options: .new, context: nil)
}
override public func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard let _ = object as? WKWebView else { return }
guard let keyPath = keyPath else { return }
guard let change = change else { return }
switch keyPath {
case "loading":
if let val = change[NSKeyValueChangeKey.newKey] as? Bool {
//do something!
}
default:
break
}
}
deinit {
removeObserver(self, forKeyPath: "loading")
}
}
I want to create a custom view that display it when an action occurs (like changed property) on another object (subclass of UIControl)
My approach was it
create a protocol whose UIControl objects can conform
create my custom view in which I can observe my delegate
Unfortunately, it's not work, worse, it's crash because compiler say "its not kvc-compliant"
Bellow, my code :
protocol CustomDelegate: class where Self : UIControl {
func respondeToControl() -> Bool
}
class CustomView: UIView {
weak var delegate: CustomDelegate? {
didSet{
observeIfControlIsPressed()
}
}
func observeIfControlIsPressed(){
if delegate != nil {
let keypath = delegate! as! UIControl
keypath.addObserver(keypath,
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
}
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("background changed")
}
}
My question is, how I can re-design my solution to make it
work ?
You addObserver is wrong, the "observer" should be self no "keypath"
Try this:
keypath.addObserver(self, forKeyPath: "backgroundColor", options: [.new, .old], context: nil)
You did not read your error correctly.. For example:
protocol CustomDelegate : class where Self : UIControl {
func respondeToControl() -> Bool
}
class CustomView: UIView {
weak var delegate: CustomDelegate? {
didSet{
observeIfControlIsPressed()
}
}
func observeIfControlIsPressed(){
if delegate != nil {
let keypath = delegate! as! UIControl
keypath.addObserver(keypath,
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
}
}
open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
print("background changed")
}
}
class Foo : UIControl, CustomDelegate {
func respondeToControl() -> Bool {
return true
}
}
class ViewController: UIViewController {
let testBlock = UIView()
override func viewDidLoad() {
super.viewDidLoad()
let view = CustomView()
let button = Foo()
view.delegate = button
button.backgroundColor = UIColor.red
}
}
Throws an exception:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<Meh.Foo: 0x7fc977c1a0c0; baseClass = UIControl; frame = (0 0; 0 0); layer = <CALayer: 0x608000226480>>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled.
Key path: backgroundColor
It means Foo is observing foo but did not implement the observeValueForKeyPath function..
Now why is that? Well.. it's because you did:
keypath.addObserver(keypath, //keypath is observing itself..
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
where keypath is your delegate.. and is adding observer on itself..
It should be:
keypath.addObserver(self, //self is observing keypath..
forKeyPath: "backgroundColor",
options: [NSKeyValueObservingOptions.new, NSKeyValueObservingOptions.old],
context: nil)
The first parameter of addObserver needs to be the class that wants to do the observing.
That change will cause it to print: background changed.. Don't forget to remove the observer later.
I have an ImageView called - (woowwww): ImageView
#IBOutlet weak var ImageView: UIImageView!
... and I'd like to fire an event every time the transform-property of the ImageView changed.
An event should get called every time the ImageView get's rotated by maybe something like this:
self.ImageView.transform = CGAffineTransform(rotationAngle: CGFloat(10))
To achieve this I tried to use some kind of KVO but obviously it is not working as expected...
override func viewDidLoad() {
super.viewDidLoad()
self.ImageView.addObserver(self.ImageView.transform, forKeyPath: "test", options: .new, context: nil)
}
func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutableRawPointer) {
print("changed")
}
Any help would be very appreciated! Thanks.
Set "transform" in keyPath like below:
imgView.addObserver(self, forKeyPath: "transform", options: .new, context: nil)
then add below code:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] as? NSObject {
print("Changed \(newValue)")
}
Hope it will help you.
keyPath should be nameOfObject.property so if imageView name is dd to listen to transform it's should dd.transform
Try this
class ViewController: UIViewController {
#IBOutlet weak var dd: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.addObserver(self, forKeyPath: "dd.transform", options: .new, context: nil)
self.dd.transform = CGAffineTransform(rotationAngle: CGFloat(10))
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let newValue = change?[.newKey] as? NSObject {
print("Changed \(newValue)")
}
}}
When I try to observe the progress of a NSBundleResourceRequest, observeValue(forKeyPath: object: change: context:) is not called for the .new observing option. Therefore, the NSProgressIndicator isn't updated. Here is the setup and code:
Setup:
Xcode 8.3.1
Deployment Target iOS 10.3
Device: iPad 4
Resource tags (368 KB) are located located in Download Only On Demand and consist of thumbnails displayed in a UICollectionView. Thumbnail images are located in MyCollection.xcassets. All IBOutlets are connected.
Images are correctly displayed in the collection view, but progress bar remains at zero.
Code:
final class MyCollectionVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
var myCollectionVCResourceRequest: NSBundleResourceRequest!
var myCollectionVCResourceRequestLoaded = false
static var myCollectionProgressObservingContext = UUID().uuidString
private let designThumbnailTags: Set<String> = ["Resource1", "Resource2", "Resource3"]
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadOnDemandResources()
}
func loadOnDemandResources() {
myCollectionVCResourceRequest = NSBundleResourceRequest(tags: designThumbnailTags)
myCollectionVCResourceRequest.progress.addObserver(self, forKeyPath: "fractionCompleted", options: [.new, .initial], context: &MyCollectionVC.myCollectionProgressObservingContext)
myCollectionVCResourceRequest.beginAccessingResources(completionHandler: { (error) in
print("Complete: \(self.myCollectionVCResourceRequest.progress.fractionCompleted)") // Prints Complete: 0.0
self.myCollectionVCResourceRequest.progress.removeObserver(self, forKeyPath: "fractionCompleted", context: &MyCollectionVC.myCollectionProgressObservingContext)
OperationQueue.main.addOperation({
guard error == nil else { self.handleOnDemandResourceError(error! as NSError); return }
self.myCollectionVCResourceRequestLoaded = true
self.updateViewsForOnDemandResourceAvailability()
self.fetchDesignThumbnailsWithOnDemandResourceTags(self.myCollectionVCResourceRequest) // Correctly creates Core Data Instances
})
})
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &MyCollectionVC.myCollectionProgressObservingContext {
OperationQueue.main.addOperation({
let progressObject = object as! Progress
self.progressView.progress = Float(progressObject.fractionCompleted)
print(Float(progressObject.fractionCompleted)) // Prints 0.0 as a result of including the .initial option
self.progressDetailLabel.text = progressObject.localizedDescription
})
}
else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
}
}
}