I am taking an online iOS course provided by Stanford. In the sample code,
#IBOutlet weak var scrollView: UIScrollView! {
didSet {
scrollView.contentSize = imageView.frame.size
// all three of the next lines of code
// are necessary to make zooming work
scrollView.delegate = self
scrollView.minimumZoomScale = 0.03
scrollView.maximumZoomScale = 1.0
}
}
However, if I remove scrollView.delegate = self, this scroll view still works on the simulator.
My questions:
Is it necessary to set scrollview.delegate as self? Why or why not?
What does self refer to? command + left click locate "did set".
You do not have to set the delegate for scrollView. You only do it if you want to react delegate methods that scrollView calls. For example, if you want to do something when user performs a scroll, you need to set the delegate and implement func scrollViewDidScroll(scrollView: UIScrollView) method. Everytime user scrolls, you will be able to react in this method's body.
Self refers to the class that holds this variable. In this case it will be probably your UIViewController
Define, it still works? I mean if you can move it with touch, yeah it will work.
The reason for
scrollView.delegate = self
is that it allows you to add code which can execute upon scroll began, scroll ended etc. That way you have customization points to alter the behavior or actions of scrollview. Without that little code the delegate code will never get called.
Makes sense?
What does self refer to?
self is the object that's executing the code that refers to self. The scrollview instance variable defined in your code snippet is part of some class; in this case it's surely a view controller. The difference between an object and a class is like the difference between a peach pie and a recipe for peach pie: the recipe is a specification that tells you all about peach pies, but you can't eat the recipe, whereas an actual peach pie is a distinct thing, and you can make several similar pies from a single recipe. Anyway, self is the name of the specific object that's executing the didSet method associated with the scrollview variable.
Is it necessary to set scrollview.delegate as self? Why or why not?
A UIScrollView object has a delegate property the you can set to any object that knows how to be a scroll view's delegate, i.e. any object that implements the UIScrollViewDelegate protocol. But since views are almost always managed by a view controller of some sort, it's pretty typical to have the view controller that manages the scroll view also act as it's delegate. The scroll view will work just fine with no delegate at all; setting up a delegate is only important if you want to do something in response to changes in the scroll view, or otherwise modify its behavior somehow. So, if a scroll view has a delegate, it's typically the view controller that manages that scroll view, and since it's probably also the view controller that sets up the scroll view in the first place self (meaning the view controller) is what you'll see most of the time.
Related
I'm currently building a generic form builder using a UICollectionView.
Each form element is a cell, and for checkboxes, I'm using a UIStackView inside the cell to display all the available options.
The problem is that each time I reuse the cell, even if I remove all the arrangedSubviews, they stay in the view with the new one.
The following code is a simplified version of what I'm doing:
class cell: UICollectionViewCell {
#IBOutlet weak var stackView: UIStackView!
func setup(options: [String]) {
for option in options {
let label = UILabel()
label.text = option
stackView.addArrangedSubview(label)
}
}
override func prepareForReuse() {
super.prepareForReuse()
optionsStackView.arrangedSubviews.forEach({ optionsStackView.removeArrangedSubview(view: $0) })
}
}
Instead of that, my current workaround is to hide() each arrangedSubview in prepareForReuse() instead of removing them, but I don't like that.
If you read the Xcode docs on the removeArrangedSubview method, they say:
Discussion: This method removes the provided view from the stack’s
arrangedSubviews array. The view’s position and size will no longer be
managed by the stack view. However, this method does not remove the
provided view from the stack’s subviews array; therefore, the view is
still displayed as part of the view hierarchy.
To prevent the view from appearing on screen after calling the stack’s
removeArrangedSubview: method, explicitly remove the view from the
subviews array by calling the view’s removeFromSuperview() method, or
set the view’s isHidden property to true.
So you need to also remove the subviews from the stack view. (I also struggled with this when I first started using stack views.)
Edit:
In fact, as #kid_x points out, simply removing the subview with removeFromSuperView() works. I'm not sure what the point of removeArrangedSubview() is, TBH.
Edit #2:
I would advise against using removeArrangedSubview() at all. Instead, do one of the following:
Option 1:
If you need to remove a view from a stack view permanently, simply use removeFromSuperView().
Option 2:
If you want to remove views from your stack view and then put them back later, simply toggle the child view's isHidden property, as mentioned in suprandr's answer. The stack view will close up the empty space and reposition the remaining views as if you removed them.
In many cases, since removing from a superview in a reusable view could (and mostly, will) cause the view to be released, in a UIStackView simply putting
dynamicView.isHidden = false
to remove and
dynamicView.isHidden = true
to add again, will cause the stack view to rearrange the other arranged views.
I know theres countless similar questions on this that either all result in using flexible height/width or setting translatesAutoresizingMaskIntoConstraints to false.
I have add a view using an extension I created:
extension UIView {
func addView(storyboard: String, viewIdentier: String) {
let story = UIStoryboard(name: storyboard, bundle: nil)
let subview = story.instantiateViewController(withIdentifier: viewIdentifier)
subview.view.frame = self.bounds
self.addSubview(subview.view)
}
}
If I use this to initialise a view, in the ViewDidAppear everything works fine. But if its in the view did load then the constraints are all over the place because the contrainView that contains the view has its own constraints that are not yet loaded.
I currently use like this:
#IBOutlet weak var container: UIView!
override func viewDidLoad() {
container.addView(storyboard: "Modules", viewIdentifier: "subview")
}
I don't want to initialise the view in the ViewDidAppear because of reasons. Is there any way of fixing this so it works as expected? Or reload the subview constraints after the superview has loaded?
Also I've really tried using other solutions. I can't make this work so would really appreciate some help.
There is no way to do things in the hard-coded manner your code proposes. You are setting the frame of the subview based on self.bounds. But in viewDidLoad, we do not yet know the final value for self.bounds. It is too soon.
The appropriate place to do something that depends on knowing layout values is after layout has taken place, namely in viewDidLayoutSubviews. Be aware that this can be called many times in the course of the app's lifetime; you don't want to add the subview again every time that happens, so use a Bool flag to make sure that you add the subview only once. You might, on subsequent resizes of the superview, need to do something else in viewDidLayoutSubviews in order to make appropriate adjustments to the subview.
But the correct way to do this kind of thing is to add the subview (possibly in viewDidLoad) and give it constraints so that its frame will henceforth remain correct relative to its superview regardless of when and how the superview is resized. You seem, in your question, to reject that kind of approach out of hand, but it is, nevertheless, the right thing to do and you should drop your resistance to it.
I currently use a UITableViewController (PFQueryTableViewController), I would like to display a UIView over the top of the TableView.
Ideally I'd like to do this in the storyboard, so I can easily add additional Labels/buttons to it. However, I can't seem to figure out how or if you can do it in storyboard. Is that right?
I have tried it programmatically. I first created the variable at the top:
var filterLabelView:UIView = UIView()
I then added the following in ViewDidLoad :
filterLabelView.frame = CGRect(x: self.view.frame.width/2, y: self.view.frame.height/2, width: self.view.frame.width, height: 40)
filterLabelView.center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2)
filterLabelView.backgroundColor = UIColor.redColor()
self.view.addSubview(filterLabelView) // See below
I also tried:
self.view.insertSubview(filterLabelView, aboveSubview: tableView)
This creates the red UIView, but it seems to be embedded to the tableview, as when I scroll up and down, the View moves with it.
What I want to create
Ideally, I want the view to sit at the bottom of the screen, and not move when the user scrolls. An example screenshot of how I want it look is below:
I have read that the best way is to use a UIViewController with a UITableView inside it, but I'd rather not take that approach seeing how much my app is already built up.
Can anyone help me create this look? Thanks in advance
You have to derive from UIViewController to get full layout control.
Then simply add a UITableView instance in Storyboard. Constrain it normally, edge-flush, top-flush, and have customView.top = tableView.bottom. Make a normal outlet to your controller.
You just need to remember to make your custom UIViewController adopt the usual dataSource and delegate protocols, as well as assigning itself as those roles to the properties of the UITableView on initialization (usually viewDidLoad()).
There's one more finesse related to clearing the selected cell when on viewDidAppear(), but there's nothing else special about a UITableViewController -- it's just a UIViewController with a built-in tableView property and automatically assigned delegates and a very inflexible layout.
ADDENDUM based on comment about how to get around this: It's sort of like asking if there is any way to make a screwdriver drive a nail into wood. Just use the right tool. The top-level view property of a UITableViewController is the UITableView itself. See below, a stock UITableViewController with nothing else, zero code, in layout debug mode.
This means the entire tree of views is embedded in a scrolled entity, so all subviews will scroll with it.
It really shouldn't be that big a deal to re-do this one VC in Storyboard. After all, your rendering code in cellForRowAtIndexPath and your dataSource and delegate generally, don't change at all.
ADDENDUM 2:
Not knowing Parse intimately, this may not be possible. (!! Thanks, Parse.) If Parse doesn't supply you with a separable PFQueryTableView that you can embed yourself, the following workaround may be possible, but you'll need to understand the Objective-C code:
https://www.parse.com/questions/pfquery-tableview-for-uiviewcontroller
Let's call this UIView subclass - SomeClass. This SomeClass is a part of a static library. Some customer will use this library and will add instances of this SomeClass to the cells of his (customer's) table view.
I (SomeClass) need to determine when the SomeClass "enters" screen (will become visible), and when will "exit" screen (will become non-visible).
I can use didMoveToWindow: method and then check self.window for nil. BUT, there is a problem, SomeClass gets this event, before it is actually visible, because of cells "preparation" by table view concept. And I need to know for sure, it is 100% visible by some user.
One way to determine is by using scrollViewDidScroll:. Suppose SomeClass will get scroll view by using iteration on super views and will subscribe as a delegate to found scroll view. But he will be removed by some cell that will subscribe itself as a delegate to scroll view. So I need to invent here some solution for this. For example, in Android, there is possibility to add observer, in that case SomeClass is always a listener and is not overriding any other listener. There is many to one relation in Android, not like in iOS, one to one.
The other way, I can enable some timer in didMoveToWindow: when SomeClass becomes visible, that will check each X time, its frame. The timer will be disabled, when SomeClass will go from screen.
Probably there is a way to check at low level, without using scroll view and timer on some low-level redraw method. Is it possible?
So what is the best (will use less resources / good design) method?
You can use CGRectIntersectsRect to check if the cell's frame intersects with the frame of your custom view. Aside from that, didMoveToWindow is the method you are looking for.
If as you say the table view cell will always have SomeClass as a subview, then it would make more sense to use UITableViewDelegate tableView:willDisplayCell:forRowAtIndexPath:.
I'm working on some custom UIView-based input controls, and I'm trying to ascertain proper practice for setting up the view. When working with a UIViewController, it's fairly simple to use the loadView and related viewWill, viewDid methods, but when subclassing a UIView, the closest methosds I have are `awakeFromNib, drawRect, and layoutSubviews. (I'm thinking in terms of setup and teardown callbacks.) In my case, I'm setting up my frame and internal views in layoutSubviews, but I'm not seeing anything onscreen.
What is the best way to ensure that my view has the correct height and width that I want it to have? (My question applies regardless of if I'm using autolayout, although there might be two answers.) What's the proper "best practice"?
Apple defined pretty clearly how to subclass UIView in the doc.
Check out the list below, especially take a look at initWithFrame: and layoutSubviews. The former is intended to setup the frame of your UIView whereas the latter is intended to setup the frame and the layout of its subviews.
Also remember that initWithFrame: is called only if you are instantiating your UIView programmatically. If you are loading it from a nib file (or a storyboard), initWithCoder: will be used. And in initWithCoder: the frame hasn't been calculated yet, so you cannot modify the frame you set up in Interface Builder. As suggested in this answer you may think of calling initWithFrame: from initWithCoder: in order to setup the frame.
Finally, if you load your UIView from a nib (or a storyboard), you also have the awakeFromNib opportunity to perform custom frame and layout initializations, since when awakeFromNib is called it's guaranteed that every view in the hierarchy has been unarchived and initialized.
From the doc of NSNibAwaking (now superseded by the doc of awakeFromNib):
Messages to other objects can be sent safely from within awakeFromNib—by which time it’s assured that all the objects are unarchived and initialized (though not necessarily awakened, of course)
It's also worth noting that with autolayout you shouldn't explicitly set the frame of your view. Instead you are supposed to specify a set of sufficient constraints, so that the frame is automatically calculated by the layout engine.
Straight from the documentation:
Methods to Override
Initialization
initWithFrame: It is recommended that you implement this method. You can also implement custom initialization methods in addition to,
or instead of, this method.
initWithCoder: Implement this method if you load your view from an Interface Builder nib file and your view requires custom
initialization.
layerClass Implement this method only if you want your view to use a different Core Animation layer for its backing store. For example,
if you are using OpenGL ES to do your drawing, you would want to
override this method and return the CAEAGLLayer class.
Drawing and printing
drawRect: Implement this method if your view draws custom content. If your view does not do any custom drawing, avoid overriding this
method.
drawRect:forViewPrintFormatter: Implement this method only if you want to draw your view’s content differently during printing.
Constraints
requiresConstraintBasedLayout Implement this class method if your view class requires constraints to work properly.
updateConstraints Implement this method if your view needs to create custom constraints between your subviews.
alignmentRectForFrame:, frameForAlignmentRect: Implement these methods to override how your views are aligned to other views.
Layout
sizeThatFits: Implement this method if you want your view to have a different default size than it normally would during resizing
operations. For example, you might use this method to prevent your
view from shrinking to the point where subviews cannot be displayed
correctly.
layoutSubviews Implement this method if you need more precise control over the layout of your subviews than either the constraint or
autoresizing behaviors provide.
didAddSubview:, willRemoveSubview: Implement these methods as needed to track the additions and removals of subviews.
willMoveToSuperview:, didMoveToSuperview Implement these methods as needed to track the movement of the current view in your view
hierarchy.
willMoveToWindow:, didMoveToWindow Implement these methods as needed to track the movement of your view to a different window.
Event Handling:
touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:, touchesCancelled:withEvent: Implement
these methods if you need to handle touch events directly. (For
gesture-based input, use gesture recognizers.)
gestureRecognizerShouldBegin: Implement this method if your view handles touch events directly and might want to prevent attached
gesture recognizers from triggering additional actions.
This still comes up high in Google. Below is an updated example for swift.
The didLoad function lets you put all your custom initialization code. As others have mentioned, didLoad will be called when a view is created programmatically via init(frame:) or when the XIB deserializer merges a XIB template into your view via init(coder:)
Aside: layoutSubviews and updateConstraints are called multiple times for the majority of views. This is intended for advanced multi-pass layouts and adjustments when a view's bounds changes. Personally, I avoid multi-pass layouts when possible because they burn CPU cycles and make everything a headache. Additionally, I put constraint code in the initializers themselves as I rarely invalidate them.
import UIKit
class MyView: UIView {
//-----------------------------------------------------------------------------------------------------
//Constructors, Initializers, and UIView lifecycle
//-----------------------------------------------------------------------------------------------------
override init(frame: CGRect) {
super.init(frame: frame)
didLoad()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
didLoad()
}
convenience init() {
self.init(frame: CGRectZero)
}
func didLoad() {
//Place your initialization code here
//I actually create & place constraints in here, instead of in
//updateConstraints
}
override func layoutSubviews() {
super.layoutSubviews()
//Custom manually positioning layout goes here (auto-layout pass has already run first pass)
}
override func updateConstraints() {
super.updateConstraints()
//Disable this if you are adding constraints manually
//or you're going to have a 'bad time'
//self.translatesAutoresizingMaskIntoConstraints = false
//Add custom constraint code here
}
}
There's a decent summary in the Apple documentation, and this is covered well in the free Stanford course available on iTunes. I present my TL;DR version here:
If your class mostly consists of subviews, the right place to allocate them is in the init methods. For views, there are two different init methods that could get called, depending on if your view is being instantiated from code or from a nib/storyboard. What I do is write my own setup method, and then call it from both the initWithFrame: and initWithCoder: methods.
If you're doing custom drawing, you indeed want to override drawRect: in your view. If your custom view is mostly a container for subviews, though, you probably won't need to do that.
Only override layoutSubViews if you want to do something like add or remove a subview depending on if you're in portrait or landscape orientation. Otherwise, you should be able to leave it alone.
layoutSubviews is meant to set frame on child views, not on the view itself.
For UIView, the designated constructor is typically initWithFrame:(CGRect)frame and you should set the frame there (or in initWithCoder:), possibly ignoring passed in frame value. You can also provide a different constructor and set the frame there.