Programatically enable/disable pinch to zoom on WKWebView and viewForZoomingInScrollView - swift

I have a WKWebView and I want to programatically enable/disable pinch to zoom.
What should I return when I want to enable pinch to zoom?
wkWebView!.scrollView breaks with
'The view returned from viewForZoomingInScrollView: must be a subview
of the scroll view. It can not be the scroll view itself.'
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
if pinchToZoom {
return ???
} else {
return nil
}
}

if (pinchToZoom)
{
for (UIView *subScrollView in [scrollView subviews])
{
if ([subScrollView isKindOfClass:NSClassFromString(#"WKContentView")])
{
return subScrollView;
}
}
return nil;
}
else
{
return nil;
}

My solution was to set self as webView.scrollView's delegate only when disabling zoom, and reset it to nil if zoom is enabled.
var pinchToZoom: Bool {
didSet {
// only set self as delegate when disabling zoom
webView.scrollView.delegate = pinchToZoom ? nil : self
}
}
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return nil
}
This way, as soon as pinchToZoom is set, the delegate updates to enable/disable zooming. No private API needed. Hope this helps!

Related

How to disbale scrolling in nscollectionview using swift on some specific condition

I have an NSCollectionView and have to disable its scrollview scrolling property so that it doesn't get reloaded on some specific condition and show user an alert if he tries to scroll.
Any delegate method which is called when a view is scrolled which can restrict scrolling and reloading of collection view data would help.
Thanks in advance.
Subclass NSCollectionView and override scrollWheel
import Cocoa
class YourCollectionView: NSCollectionView {
var disableScroll = true
override func scrollWheel(with event: NSEvent) {
if disableScroll {
return
} else {
super.scrollWheel(with: event)
}
}
}
You can do with the help of scrollViewDidScroll method of ScrollViewDelegate.You can use this method because collectionview is subclass of scrollview.
For Example :
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print("collectionView Content : \(collectionView.contentOffset.y)")
if collectionView.contentOffset.y >= 100
{
collectionView.isScrollEnabled = false
}
else
{
collectionView.isScrollEnabled = true
}
}
I hope its work for you.

Detecting if a UIScrollView has the height to scroll

I have a UIScrollView that sometimes has enough contentHeight to scroll and sometimes doesn't. I also have a button that's predicated on the user scrolling to the bottom of the scroll view.
Without the user taking an action, how do I detect if the scroll view has the contentHeight to scroll so I can set the default isEnabled of the button appropriately?
Thanks!
You can create an extension on UIScrollView
extension UIScrollView {
var contentUntilBotttom: CGFloat {
return max(contentSize.height - frame.size.height - contentOffset.y, 0)
}
var isAtTheBottom: Bool {
return contentUntilBotttom == 0
}
}
In case you are using RxSwift you can observe changes like:
extension Reactive where Base == UIScrollView {
var isAtTheBottom: ControlEvent<Bool> {
let source = contentOffset
.map({ _ -> Bool in
return self.base.isAtTheBottom
})
return ControlEvent(events: source)
}
}
// So then subscribe to it
scrollView.rx.isAtTheBottom
.subscribe(onNext: { (isAtTheBottom) in
// Update anithing you need
})

Remove any view from any where e.g from window

I have 2 views on the screen, one is overlayView at the bottom and introView at the top of that overlayView. When I tap(tapToContinueAction) on the screen they should both hide or remove themselves.
extension UIView {
....
func hideView(view: UIView, hidden: Bool) {
UIView.transition(with: view, duration: 0.5, options: .transitionCrossDissolve, animations: {
view.isHidden = hidden
})
}
}
class IntroScreen
#IBAction func tapToContinueAction(_ sender: UITapGestureRecognizer) {
self.hideView(view: self, hidden: true)
}
--
class OverlayView : UiView {
...
}
In current situation i can hide introScreen only and i dont know how the other class's action can effect the overlayView at the same time and hide that view as well. Any idea?
You have two different classes for your views. Make an extension of your window to remove your specific views just like I have made removeOverlay and removeIntroView both these computed properties will go and search in subviews list of window and check each view with their type and remove them. Thats how you can remove any view form any where.
class OverLayView: UIView {}
class IntroView: UIView {
#IBAction func didTapYourCustomButton(sender: UIButton) {
let window = (UIApplication.shared.delegate as! AppDelegate).window!
window.removeOverlay
window.removeIntroView
}
}
extension UIWindow {
var removeOverlay: Void {
for subview in self.subviews {
if subview is OverLayView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
var removeIntroView: Void {
for subview in self.subviews {
if subview is IntroView {
subview.removeFromSuperview()// here you are removing the view.
subview.hidden = true// you can hide the view using this
}
}
}
}

How to determine the active textfield

How to determine which text field is active ? (which one have the focus on it).
I found this code in objective-C but don't know how if it is still working and how to translate it in swift
NSResponder *firstResponder = [[NSApp keyWindow] firstResponder];
if ([firstResponder isKindOfClass:[NSText class]] && [(id)firstResponder delegate] == mySearchField) {
NSLog(#"Yup.");
}
Here you go:
var responder = window.firstResponder
if responder.isKind(of: NSText.self) {
let fieldEditor = responder as! NSText
responder = fieldEditor.delegate as! NSResponder
}
At the end is responder the focused control. If the focused control is a NSTextField then the first responder is the field editor, a NSTextView inside the text field. The delegate of the field editor is the control.
Umm here is how I would do it in iOS if anyone is interested. I am not familiar with how Mac OS deals with firstResponder, but it looks to be quite different.
func findFirstResponder(in view: UIView) -> UIView? {
for subview in view.subviews {
if subview.isFirstResponder {
return subview
}
if let firstReponder = findFirstResponder(in: subview) {
return firstReponder
}
}
return nil
}
You can do an if else and iterate over each textField so you can know what textField is the firstResponder.
func methodForDiscoverTheFirstResponder {
if myTextField.isFirstResponder {
//do stuff
} else if mySecondTextField.isFirstResponder {
//do stuff
}
...
}

check if UIView is in UIScrollView visible state

What is the easiest and most elegant way to check if a UIView is visible on the current UIScrollView's contentView? There are two ways to do this, one is involving the contentOffset.y position of the UIScrollView and the other way is to convert the rect area?
If you're trying to work out if a view has been scrolled on screen, try this:
CGRect thePosition = myView.frame;
CGRect container = CGRectMake(scrollView.contentOffset.x, scrollView.contentOffset.y, scrollView.frame.size.width, scrollView.frame.size.height);
if(CGRectIntersectsRect(thePosition, container))
{
// This view has been scrolled on screen
}
Swift 5: in case that you want to trigger an event that checks that the entire UIView is visible in the scroll view:
extension ViewController: UIScrollViewDelegate {
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if scrollView.bounds.contains(targetView.frame) {
// entire UIView is visible in scroll view
}
}
}
Implement scrollViewDidScroll: in your scroll view delegate and calculate manually which views are visible (e.g. by checking if CGRectIntersectsRect(scrollView.bounds, subview.frame) returns true.
updated for swift 3
var rect1: CGRect!
// initialize rect1 to the relevant subview
if rect1.frame.intersects(CGRect(origin: scrollView.contentOffset, size: scrollView.frame.size)) {
// the view is visible
}
I think your ideas are correct. if it was me i would do it as following:
//scrollView is the main scroll view
//mainview is scrollview.superview
//view is the view inside the scroll view
CGRect viewRect = view.frame;
CGRect mainRect = mainView.frame;
if(CGRectIntersectsRect(mainRect, viewRect))
{
//view is visible
}
José's solution didn't quite work for me, it was detecting my view before it came on screen. The following intersects code works perfect in my tableview if José's simpler solution doesn't work for you.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let viewFrame = scrollView.convert(targetView.bounds, from: targetView)
if viewFrame.intersects(scrollView.bounds) {
// targetView is visible
}
else {
// targetView is not visible
}
}
Solution that takes into account insets
public extension UIScrollView {
/// Returns `adjustedContentInset` on iOS >= 11 and `contentInset` on iOS < 11.
var fullContentInsets: UIEdgeInsets {
if #available(iOS 11.0, *) {
return adjustedContentInset
} else {
return contentInset
}
}
/// Visible content frame. Equal to bounds without insets.
var visibleContentFrame: CGRect {
bounds.inset(by: fullContentInsets)
}
}
if scrollView.visibleContentFrame.contains(view) {
// View is fully visible even if there are overlaying views
}