I'm trying to hook up a scroll view using Interface Builder, and the UIScrollViewDelegate.scrollViewDidScroll method isn't getting invoked on scroll.
In IB, I have a view controller that uses my PagedScrollViewController as a custom class. In that class, I have:
class PagedScrollViewController: UIViewController, UIScrollViewDelegate {
func scrollViewDidScroll(scrollView: UIScrollView!) {
println("scrollViewDidScroll")
}
}
Unfortunately, that println is never getting invoked. I know that PagedScrollViewController is being connected correctly because if I add a viewDidLoad method, that gets invoked. Is there something extra I need to do to attach the delegate other than setting the custom class?
Turns out I needed to attach the scroll view's delegate to the the controller. Here's what worked for me:
class PagedScrollViewController: UIViewController, UIScrollViewDelegate {
#IBOutlet var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView.delegate = self
}
Related
I followed this tutorial to create a Snapchat-Like Menu: https://www.youtube.com/watch?v=1_daE3IL_1s
In short, the idea was to create custom views with XIB, and add them into a scrollview.
I would like to add a "back" button to one of the custom views so that the scrollview will automatically scroll back to its initial view when the button is tapped. The Idea I had was to write a code inside the custom view's IBAction function to call UIScrollview's content offset delegate function.
the following is the code in View Controller:
class ViewController: UIViewController, UIScrollViewDelegate {
//I'm naming the Scroll View as "scrollView"
#IBOutlet weak var scrollView: UIScrollView! {
didSet{
scrollView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var v1 : View1 = View1(nibName: "View1", bundle: nil)
var v2 : View2 = View2(nibName: "View2", bundle: nil)
self.addChild(v1)
self.scrollView.addSubview(v1.view)
v1.didMove(toParent: self)
self.addChild(v2)
self.scrollView.addSubview(v2.view)
v1.didMove(toParent: self)
var v2Frame : CGRect = v2.view.frame
v2Frame.origin.x = self.view.frame.width
v2.view.frame = v2Frame
self.scrollView.contentSize = CGSize(width: self.view.frame.width * 2, height: self.view.frame.height)
// self.scrollView.contentSize = CGSize(self.view.frame.width * 2, self.view.frame.size.height)
}
}
The following ht the code in View2:
class View2: UIViewController {
#IBOutlet weak var buttonHome: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func buttonHomeAction(_ sender: Any) {
//how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view?
}
So my question is how to access scrollview.setContentOffset from ViewController to scroll the view back to the first view? Am I on the right track? Or are there any alternative methods where I can tap a button and scroll the view back to its first page?
Thank you!
I can think of two good options here:
1) Hold a weak reference in View2 of the scrollView. in View2 add:
weak var scrollView: UIScrollView?
and in ViewController, after view2 creation add:
view2.scrollView = self.scrollView
now, in buttonHomeAction you'll have access to the scrollView.
2) If you are familiar with Apple's delegation pattern, you can add a delegate, which can be a weak reference of the presenting view controller.
Create a protocol for the delegate, using named by the view holds the delegate. so, in this case: View2Delegate
and add a method that describes the action.
protocol View2Delegate: AnyObject {
func buttonTapped()
}
so, in View2 add:
weak var delegate: View2Delegate?
and in ViewController, after view2 creation add:
view2.delegate = self
now, in ViewController implement the protocol method and access scrollView:
extension ViewController: View2Delegate {
func buttonTapped() {
// access scrollView as self.scrollView
}
}
I have two view controllers SymbolsVC and ItemsVC. Both have a UIActivityIndicatorView which has been outlet by the name of spinner in each view controller like this:
class SymbolsVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
}
class ItemsVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
}
Now I need to write a lot of shared code between these view controllers, for example, a function which would start the spinner in its respective viewcontroller. So I have created an extension for UIViewController like this:
extension UIViewController {
func startSpinner() {
spinner.startAnimating()
}
}
However, this gives Use of unresolved identifier 'spinner' error.
What am I missing?
EDIT FOR FURTHER CLARIFICATION: My extension is actually an IAP. This IAP can be called from any number of ViewControllers. When one of the ViewControllers, call the IAP function which is in the extension, it is the flow of the program in that IAP function which determines when to start/stop the spinner. Hence it needs to be done within the extension.
When the compiler sees this:
extension UIViewController {
func startSpinner() {
spinner.startAnimating()
}
}
it thinks every instance of UIViewController in your module contains this spinner property, which of coarse is wrong.
Since as a rule, you can't place regular {set get} properties inside extension, my solution would be placing the spinner inside a base view controller SpinnerBaseVC, which inherits UIViewController.
Then, inherit SpinnerBaseVC with whatever class you want to utilize the spinner property.
class SpinnerBaseVC: UIViewController {
#IBOutlet weak var spinner: UIActivityIndicatorView!
func startSpinner() {
spinner.startAnimating()
}
}
// MARK: Classes which can naturally access `spinner` and `startSpinner`
class SymbolsVC: SpinnerBaseVC {
// Here you can access spinner
}
class ItemsVC: SpinnerBaseVC {
// Here you can access spinner
}
I need to add delegate to textfields; my understanding is it can be done two ways:
we go to file and viewController.swift and under class, after UIviewcontroller we add comma and then type UITextFieldDelegate .
and then u under viewDidLoad we add method and function
when on storyboard we click and drag textfield to small icon on top of view that says view controller and pick delegate
Do they both work the same? or there is difference if we do one way or the other?
and what you do if you have more than one textfields, I have 10 textfields and have 7 functions and buttons, I need to do this so I can disable button if my textfield is empty for that button.
There is no difference, as far as I know. Choose what you like more.
In first case you also need to make #IBOutlet for your UITextField(click and drag UITextField from UIStoryboard to your UIViewController code) and after that make UIViewController delegate of UITextField
For example:
import UIKit
class MyViewController: UIViewController, UITextFieldDelegate
{
#IBOutlet weak var myTextField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
myTextField.delegate = self
}
}
I am developing an mac osx application which have initial window and viewcontroller launched from main storyboard. I want to replace the content view loaded by storyboard with my view.
I am doing this -
func replaceContentView() {
parentViewController = MainViewController(nibName: "MainContainerView", bundle: nil)!
let fullScreenFrame = NSScreen.mainScreen()?.visibleFrame
self.initialWindow.setFrame(fullScreenFrame!, display: false, animate: false)
self.initialWindow.contentView = parentViewController! . view
}
Problem with this approach is that the default viewcontroller is never getting deallocated. deinit() of default viewController is not called at all.
This is causing memory leak. So how to completely remove default content view and associated viewcontroller?
Storyboards don't deal in views, they deal in viewcontrollers. What a Storyboard does when it loads a view into a window is that it creates an NSViewController and then goes
windowController.contentViewController = theViewController
That implicitly also inserts theViewController.view as the window's content view. So do the same, and all will be fine.
Marek's example is wrong, because CustomView shouldn't be an NSView subclass, it should be a CustomViewController class that owns a view containing the label etc. As a bonus, NSViewController will take care of loading the XIB for you as well.
Alternately, you could set windowController.contentViewController = nil (which will remove the old view controller and its content view) and then set your content view. But really, why fight the framework when that's exactly what NSViewController is intended for?
You can write the code in deinit method,may it will help you.
deinit {
// perform the deinitialization
}
Your contentViewController within NSWindow instance still holds strongly its old view. You have replaced just property on your NSWindow instance.
To clarify what you have done:
NSWindow holds strongly against new view
NSViewController holds strongly against old view
You should assign your new view into contentViewController.view property as well
This might be helpfull:
NSWindow.h
/* NSViewController Support */
/* The main content view controller for the window. This provides the contentView of the window. Assigning this value will remove the existing contentView and will make the contentViewController.view the main contentView for the window. The default value is nil. The contentViewController only controls the contentView, and not the title of the window. The window title can easily be bound to the contentViewController with the following: [window bind:NSTitleBinding toObject:contentViewController withKeyPath:#"title" options:nil]. Setting the contentViewController will cause the window to resize based on the current size of the contentViewController. Autolayout should be used to restrict the size of the window. The value of the contentViewController is encoded in the NIB. Directly assigning a contentView will clear out the rootViewController.
*/
#availability(OSX, introduced=10.10)
var contentViewController: NSViewController?
/* The view controller for the window's contentView. Tracks the window property of the same name.
*/
#property (strong) NSViewController *contentViewController NS_AVAILABLE_MAC(10_10);
However what you do seems incorrect if you do this on launch.
You either set custom subclass of contentView to your new nsview subclass which can load it's view from another XIB (no need for storyboard).
Abstract example:
class CustomView: NSView {
#IBOutlet var contentView: NSView!
#IBOutlet weak var label: NSTextField!
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSubviews()
}
override init(frame: CGRect) {
super.init(frame: frame)
initSubviews()
}
func initSubviews() {
let nib = NSNib(nibName: "CustomView", bundle: nil)
nib.instantiateWithOwner(self, topLevelObjects: nil)
contentView.frame = bounds
addSubview(contentView)
}
}
PS: topLevelObjects is set to nil because you hold strongly contentView. So no need to worry about memory management.
I was using this example which explains the use of NSNotification.
In my case, I have a UIViewController in which I have a UITableView. To this tableview I am assigning a dataSource and delegate programatically by instatiating my UITableViewController. So far I have not declared any inits, and thus have been using the simple init() to initialize my UITableViewController. (This UITableViewController is not on the StoryBoard).
class foo: UIViewController{
#IBOutlet weak var fooTable: UITableView!
var fooTableViewController = MyTableViewController()
override func viewDidLoad(){
super.viewDidLoad()
fooTable.delegate = fooTableViewController
fooTable.dataSource = fooTableViewController
}
}
class MyTableViewController: UITableViewController {
override func viewDidLoad(){
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "notificationReceived", name: "TEST_NOTIFICATION", object: nil)
}
}
If I try to add the observer in viewDidLoad() of the UIViewController, it does not work.
So my question is: Does using the NSNotification require the usage of init(coder aDecoder: NSCoder)? If so, then what is the correct way to initialize using this init in swift? How should I be instantiating MyTableViewController in my UIViewController instance foo?
viewDidLoad is only called when the view of a view controller is loaded - in the code you're showing you create a table view controller subclass, assign it as the datasource and delegate of another table view (confusing, as it will already be the datasource and delegate of its own table view), but never actually do anything with the table view controller's view.
This means that viewDidLoad will not be called.
You should probably be adding your table view controller's tableView as a subview and also adding it as a child view controller so that rotation and appearance events are forwarded properly.
Note that the question and answer are nothing whatsoever to do with notification centers or Swift, but just about understanding the view controller lifecycle.
If you want a separate object to act as a datasource and delegate for your table view, great idea, but don't use a UITableViewController subclass. Just create a plain object which conforms to the data source and/or delegate protocols.