I have a UIView subclass (a graph) embedded inside of a UIScrollview inside of a UIViewController which is one of a UIPageViewControllers pages... I'm trying to disable scrolling of the UIPageViewController so that the user scan scroll to see the far left of the graph without paging back. How can I do this? Is a ScrollView even the correct tool for this job?
class HistoricalHealthDataViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var graphScrollView: UIScrollView!
#IBOutlet weak var hockeyTrackerGraphView: HockeyTrackerGraphView! {
didSet {
self.hockeyTrackerGraphView.graphableObjects = HFRGraphableObjects
}
}
var HFRGraphableObjects: [HockeyTrackerGraphableObject] = []
override func viewDidLoad() {
super.viewDidLoad()
graphScrollView.delegate = self
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
if let parentPageViewController = parent as? HistoricalPageViewController {
for gestureRecognizer in parentPageViewController.gestureRecognizers {
print("gestureRecognizer")
gestureRecognizer.isEnabled = false
}
}
}
}
You don't show the code for it, but I'm assuming you implement UIPageViewControllerDataSource protocol and somewhere you have the methods:
func pageViewController(UIPageViewController, viewControllerBefore: UIViewController) -> UIViewController? {
}
func pageViewController(UIPageViewController, viewControllerAfter: UIViewController) -> UIViewController? {
}
If you arrange for those methods to return nil while paging is disabled, the page view won't scroll to a different page.
Related
I find a lot of resources on how to hide the navigation bar on scroll, but I would like to have the navigation bar hidden on start, and then appear when starting to scroll. Like this animation from Design+Code app: https://imgur.com/a/SqDRD
You can use UIScrollViewDelegate for that.
Here is example code for hide navigation bar and tool bar with scroll:
import UIKit
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var toolBar: UIToolbar!
#IBOutlet weak var webV: UIWebView!
var lastOffsetY :CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
webV.scrollView.delegate = self
let url = "http://apple.com"
let requestURL = NSURL(string:url)
let request = NSURLRequest(URL: requestURL!)
webV.loadRequest(request)
}
//Delegate Methods
func scrollViewWillBeginDragging(scrollView: UIScrollView){
lastOffsetY = scrollView.contentOffset.y
}
func scrollViewWillBeginDecelerating(scrollView: UIScrollView){
let hide = scrollView.contentOffset.y > self.lastOffsetY
self.navigationController?.setNavigationBarHidden(hide, animated: true)
toolBar.hidden = hide
}
}
Using the willBeginDragging and didEndDragging you can accomplish what you want. Here there is a simplified version of it, you may need to modify it a little bit to obtain the desired effect you want, but it is an starting point.
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setNavigationBarHidden(true, animated: false)
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
navigationController?.setNavigationBarHidden(false, animated: true)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
navigationController?.setNavigationBarHidden(true, animated: true)
}
}
I have a view controller with a scroll view, followed by a map-like image inside the view. On top of the map image I have 16 different pin buttons pointing to different parts on a map image. As of now I can zoom in and out of the map but the 16 buttons do not follow and get scattered. How can I attach or lock the buttons onto the map image so they zoom in and out with the map. As of now inside my map view controller I have this: (thanks in advance for an answer!)
import UIKit
class MapppViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var mainScrollView: UIScrollView!
#IBOutlet weak var mapImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
self.mainScrollView.minimumZoomScale = 1.0
self.mainScrollView.maximumZoomScale = 5.0
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func viewForZooming(in scrollView: UIScrollView ) -> UIView?
{
return self.mapImageView
In a sketchy way, I presume this is the sort of thing you're after:
You can achieve that either by making the buttons subviews of the image view or (as I did here) by making the buttons and the image view subviews of a "container view" that serves no other purpose except to be the zoomable subview of the scroll view.
Here's my view hierarchy:
Here's the only code:
class ViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet weak var sv: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
self.sv.minimumZoomScale = 1
self.sv.maximumZoomScale = 2
}
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return scrollView.viewWithTag(999) // the container view
}
}
I'm trying to use KVO to observe the update change when using drag in my page view controller's child content view controller's scrollView, but when the app launch, it crashed says:
"Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'An instance 0x7ff003d3f5b0 of class KVOPageVC.ContentViewController was deallocated while key value observers were still registered with it."
Here below is my code and screenshot:
APP SCREENSHOT
CODE
PageViewController.swift
import UIKit
class PageViewController: UIPageViewController {
var pageLabels: Array<String> = ["First Page", "Second Page", "Third Page"]
override func viewDidLoad() {
super.viewDidLoad()
dataSource = self
self.setViewControllers([contentViewForPage(0)], direction: .Forward, animated: true, completion: nil)
}
func contentViewForPage(index: Int) -> ContentViewController {
let contentVC = storyboard!.instantiateViewControllerWithIdentifier("ContentVC") as! ContentViewController
contentVC.pageIndex = index
contentVC.label = pageLabels[index]
return contentVC
}
}
extension PageViewController: UIPageViewControllerDataSource {
func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == 0 || index == NSNotFound {
return nil
}
index -= 1
return contentViewForPage(index)
}
func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
let vc = viewController as! ContentViewController
var index = vc.pageIndex as Int
if index == NSNotFound {
return nil
}
index += 1
if index == self.pageLabels.count {
return nil
}
return contentViewForPage(index)
}
}
ObeserverViewController.swift
it's 'a view controller' embedded in 'content view controller's' 'Container View', when the user drag and release the scroll below, i want the emoji face to be replaced by the text "to be notified!"
import UIKit
class ObeserverViewController: UIViewController {
#IBOutlet weak var notifyLabel: UILabel!// when the user drag the scroll view and release, i hope its value will be changed accordingly.
var contentVC: ContentViewController! //the variable to hold the object
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.contentVC = self.storyboard!.instantiateViewControllerWithIdentifier("ContentVC")
self.contentVC.addObserver(self, forKeyPath: "changingLabel", options: [], context: nil)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if keyPath == "changingLabel" {
notifyLabel.text = "to be notified!"
}
}
deinit {
self.contentVC.removeObserver(self, forKeyPath: "changingLabel")
}
}
ContentViewController.swift
Page view controller's child view controllers, 3 pages in total. It includes a Scroll View and a Container View(embedded ObeserverViewController)
import UIKit
class ContentViewController: UIViewController {
var label: String!
var pageIndex: Int!
dynamic var changingLabel: String = ""
#IBOutlet weak var contentLabel: UILabel!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var containerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
contentLabel.text = label
self.scrollView.delegate = self
self.contentView.backgroundColor = UIColor.greenColor()
}
}
extension ContentViewController: UIScrollViewDelegate {
func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if scrollView.contentOffset.y < -50 {
if contentView.backgroundColor == UIColor.greenColor() {
contentView.backgroundColor = UIColor.yellowColor()
self.setValue("hah", forKey: "changingLabel")
} else {
contentView.backgroundColor = UIColor.greenColor()
self.setValue("wow", forKey: "changingLabel")
}
}
}
}
My questions is:
How can i make the emoji label text to be notified to change in one controller when i drag and release the scroll view in another controller?
Thank you very much in advance!
Firstly you are adding observer to one object and removing from another. When you are calling instanciateViewController... it will create you new object of passed view controller identifier. Than you are signing to it's changes via KVO. But in viewWillDissapear you are not getting the same object that was crated in viewWillAppear, but crating new one(it has nothing with that was created in viewWillAppear). Than you are resigning from it's notification, still as it is not the same object that you has created previously and signed to him(with KVO), such resigning won't lead to needed result. What you need to do is to save firstly created object to some variable and than resign this variable where it's needed.
Secondly you need to remove observer not only in viewWillDissapear method but as well in
deinit {
// perform the deinitialization
}
Than you will be sure that if your object is deleted than it as well will be resigned from notifications.
Your error message tells that object that was signed for notifications was marked as deleted, still it was not signed out and it will be receiving notification even if it's deleted(sound not really good as memory that he belongs to may already be used for some other objects, that may lead to undefined behavior of your app).
I have two side by side text views. How can I link them so that if one is scrolled down other automatically scrolls the same as first one in swift 2.
Set your viewController conforms to UITextViewDelegate,then set textView delegate to self,then in scrollViewDidScroll sync both contentOffSet
For example
Gif
Code
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var textview2: UITextView!
#IBOutlet weak var textview1: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textview1.delegate = self
textview2.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView == textview1{
textview2.contentOffset = textview1.contentOffset
}else{
textview1.contentOffset = textview2.contentOffset
}
}
}
I would like to make a UI that have label, table view and one button click. When click on the button, we pop up a half screen view that have lots of buttons. I want user can still click on the rest of the screen also.
So i use the approach that suggest in the post
How To Present Half Screen Modal View?
Method 2: to animate a UIView which is of size half of the existing view.
Then you have to simply follow animation of the UIView.
Here as it is just a UIView that will be added as subview to existing view, you will be able to touch the rest of the screen.
As i am newbie to the ios and swift, I would like to get some suggestions.
Now i am successfully add as subview and show in the half of the screen.
How can i implement to let subview click button result show on parent view label text?
I am thinking about parent.xib and subview.xib have the same UIVeiwController.swift. Then i can #IBOutlet and #IBAction to the same controller swift file and update the result. But don't know it is the accpetable way to do?
If not, how can the subViewController send result/event to the parent view and update in the parent view component?
You could use delegation. This keeps your view controllers decoupled, i.e. prevents the child from having a reference to its parent, which allows other view controllers to interact with the modal view controller in the same way.
class ParentViewController : UIViewController, ModalViewControllerDelegate {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
modalViewContorller.delegate = self
self.presentViewController( modalViewContorller, animated: true, completion: nil )
}
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String ) {
self.label.text = result
}
}
protocol ModalViewControllerDelegate {
func modalViewControllerDidProduceResult( modalViewController: ModalViewController, result: String )
}
class ModalViewController: UIViewController {
var delegate: ModalViewControllerDelegate?
#IBAction func buttonClicked( sender: AnyObject? ) {
delegate?.modalViewControllerDidProduceResult( self, result: "Hello!" )
}
}
You could also use a closure, which in Swift provides a more concise syntax.
class ParentViewController : UIViewController {
#IBOutlet weak var label: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let modalViewContorller = ModalViewController()
self.presentViewController( modalViewContorller, animated: true, completion: nil )
modalViewContorller.resultBlock = { (result: String) in
self.label.text = result
}
}
}
class ModalViewController: UIViewController {
var resultBlock: ((String) -> ())?
#IBAction func buttonClicked( sender: AnyObject? ) {
self.resultBlock?( "Hello!" )
}
}