I've been wondering about extending UIView with a sort of setNeedsRefresh method of my own - a method that behaves like setNeedsLayout and setNeedsDisplay but with the purpose of refreshing/reloading visible data, hiding views, etc. My goals would be to:
Be able to detach methods (particularly setters) from having to instantly refresh views.
Be able to flag a view for refresh rather than refreshing it manually (in order to optimize code execution and have a cleaner code architecture).
I was hoping to be able to extend UIView in such a way that you can call setNeedsRefresh on any view, and have it execute refreshView or some similar method before redrawing happens.
PS: is the best way to handle this through setNeedsLayout + a refresh flag?
Update #1: The following is an example of what I want to be able to do:
-(void)setAvailableForPurchase:(BOOL)availableForPurchase
{
if (_availableForPurchase != availableForPurchase)
{
_availableForPurchase = availableForPurchase;
[self setNeedsRefresh];
}
}
And then:
-(void)refreshView
{
if (self.isAvailableForPurchase)
{
self.someView.image = someImage;
self.someButton.enabled = yes;
// more code, changes several objects
}
else
{
// some other code...
}
}
This example is a very simple one on purpose to better express my requirement - the reasons why I need/want to do this are listed in my initial question.
Related
I believe everyone is familiar with
open func didAddSubview(_ subview: UIView)
This function is called whenever a new subView is added to the UIView.
I am wondering on how to achieve? Like, what is the underlying codes to achieve this?
Thanks
Edit:
what i mean in the question is the codes to achieve a non-objective-c observing function. For example, we can build a notification, so that whenever addSubView is called, the didAddSuView is called correspondently, but apparently, in this case. It is not an object-c selector which we see in normal implementation. So i am asking how do we achieve something like this, which means, how do i trigger a function when another function is called without it being an objc function.
Btw, just to clarify further. The function is implemented as function of UIView (Extension UIView), so it is not a protocol, which means it wasn't elegantly achieved as delegate.
So again, i am asking.. How can i replicate something like this?
“The underlying codes” is that you just call the “observing function” when it needs to be called. There's no magic in how UIKit calls didAddSubview. It doesn't use any features specific to Objective-C.
The iOS SDK has four methods for adding a subview to a view:
addSubview:
insertSubview:atIndex:
insertSubview:aboveSubview:
insertSubview:belowSubview:
All of these methods are wrappers for a private method, _addSubview:positioned:relativeTo:. You can check this using a disassembler, or by putting a breakpoint in didAddSubview: and looking at the stack trace to see who calls it.
The private method calls [self didAddSubview:subview] using a normal Objective-C message. It does not “build a notification”. The source code probably looks something like this:
- (void)_addSubview:(UIView *)newSubview position:(UIViewSubviewPosition)position relativeTo:(UIView *)sibling {
// Lots of bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
[newSubview removeFromSuperview];
switch (position) {
case UIViewSubviewPositionAtEnd:
[self.layer addSublayer:newSubview.layer];
break
case UIViewSubviewPositionBelowSibling:
[self.layer insertSublayer:newSubview.layer below:sibling.layer];
break;
case UIViewSubviewPositionAboveSibling:
[self.layer insertSublayer:newSubview.layer above:sibling.layer];
break;
default:
[self.layer insertSublayer:newSubview.layer atIndex:(unsigned int)position];
break;
}
[newSubview didMoveToSuperview];
[newSubview didMoveToWindow];
[self didAddSubview:newSubview];
// Lots more bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
}
In Swift, it could look like this:
enum SubviewPosition {
case atEnd
case below(UIView)
case above(UIView)
case atIndex(UInt32)
}
func _addSubview(_ newSubview: UIView, position: SubviewPosition) {
// Lots of bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
newSubview.removeFromSuperview()
switch position {
case .atEnd: layer.addSublayer(newSubview.layer)
case .below(let sibling):
layer.insertSublayer(newSubview.layer, below:sibling.layer)
case .above(let sibling)):
layer.insertSublayer(newSubview.layer, above:sibling.layer)
case .atIndex(let index):
layer.insertSublayer(newSubview.layer at:index)
}
newSubview.didMoveToSuperview()
newSubview.didMoveToWindow()
didAddSubview(newSubview)
// Lots more bookkeeping related to first responder status,
// gesture recognizers, auto layout, visual effects,
// and private implementation details…
}
This seems like it should have a simple answer, and probably does, but it's proving harder to find than I expected. As a specific example, let's say that I'm programming a chess game.
It seems like this is something I should be able to do just using CoreGraphics. It seems like using OpenGL or SpriteKit shouldn't be necessary.
I want to have a Board class that models the state of the board. Where should I declare my Board object? My impression is that it should be in the ViewController.
I want to have a view (actually a subview) that displays the current state of the board (by overloading drawRect). It should do this at the beginning, and should be updated when players make moves. How does the view access the data model to display the board state? Does giving the view a reference to the data violate MVC? If not, how would the reference be passed to the view? (I seem to just find lots of links about passing data between two ViewControllers.)
Should it instead be the ViewController "pushing" the data to the view whenever it needs to be drawn? My understanding, though, is that drawRect should not be called directly, and that instead setNeedsDisplay should be called, which will indirectly result in drawRect being called. This being the case, it's hard to see how the data would be passed.
Your code; your design decision. Nothing to comment on here.
You should have your model declaration in ViewController. True. That is how MVC works.
Having a reference of the data in a UIView DOES break MVC. Your view instance will not be independent anymore. Decoupling view and model is one of the main points of MVC and you are probably breaking it with this design.
What can you do about it?
Extending #Paulw11's comment, in your view controller you can declare a method that looks something like this :
func movePiece(somePiece : Piece, toSquare : Square) {
let pieceID = somePiece.id //I am just assuming the models structures
let pieceImageView = self.pieceImageViewFromID(id) //Assume that this method returns the reference of the image view. Assume that I am just working UIKit here.
let fromPoint : CGPoint = somePiece.origin
let toPoint : CGPoint = toSquare.coordinates
self.animateView(pieceImageView, fromPoint:fromPoint, toPoint:toPoint)
}
Note that in this design, the view is not holding any model references; the view controller will take care of setting its state and bring upon relevant animations.
If you are overriding drawRect:, then yes, for it be called, you should call setNeedsDisplay to update the changes. The view controller might call or you can add property observers to redraw itself based on a property change. One example for this could be:
class SillyView : UIView {
var drawPonies : Bool = false {
didSet {
if oldValue != drawPonies {
self.setNeedsDisplay()
}
}
}
override func drawRect(rect: CGRect) {
if drawPonies {
self.drawGoodLookingPony()
} else {
self.drawSomeOtherAnimal()
}
}
func drawGoodLookingPony() {
//Draw a good looking pony here
}
func drawSomeOtherAnimal() {
//Draw Something else
}
}
If your view controller decides to draw ponies all you have to do is, get the reference of the SillyView and set drawPonies to true.
self.sillyView.drawPonies = true
You are not passing your data model here, but important pieces of configuration information that will help the view redraw itself.
I have a customView. It has some condition like this(only example):
customView(viewsNeed: Bool)
...
if viewsNeeded {
self.addSubView(newView)
self.addSubView(newView2)
} else {
self.addSubView(newView3)
self.addSubView(newView4)
self.addSubView(newView5)
}
and then I can add this View to in my ViewController:
self.view.addSubView(customView(viewsNeeded))
What I want to know is what should I do? Write conditions like this, or make separate Views for this purpose. Something like:
View1
...
self.addSubView(newView)
self.addSubView(newView2)
View2
...
self.addSubView(newView3)
self.addSubView(newView4)
self.addSubView(newView5)
And add one of them in the ViewController:
if viewsNeeded {
self.view.addSubView(view1)
} else {
self.view.addSubView(view2)
}
What kind of View creating is better in what situation, and how should i decide this kind of things? I need some really wide answers with explanations if it's real.
If a view can have different states, you would take care of those different states within the view that has a certain responsibility. The UINavigationBar is a good example. It has a clear purpose, giving navigational context to the user, but it's state (and context) can make it appear different.
func pushNavigationItem(...) {
...
if self.items.count > 1 {
// show backButton
} else {
// hide backButton
}
}
If the different views don't work together for a shared purpose, I wouldn't group them together in a container-view, but instead add them separately, dependent on your needs in a ViewController.
override func viewDidLoad() {
if userDidBuyContent() {
// add view with bought content
} else {
// add view to buy content
}
}
And in general it's a good practice to keep your view-hierachy as flat as possible. The less views you introduce, the better your app will perform. The decision is ultimately up to you, but just keep in mind what the purpose of a view is and whether subviews contribute to that purpose or are really serving some other purpose.
there is no conceptual difference between options you've described. from MVC pattern perspective they are both slightly wrong. you don't have to add views manually, view must create its structure itself.
A common situation is to have a View Controller A, and it has some information which will be sent to View Controller B; and B will edit the information, when B finishes editing the information, B will call the delegate method to update A, and pop itself from the navigation controller.
How to handle this problem with MVVM and ReactiveCocoa?
Heavy use of ReactiveCocoa, in general, will start pushing you away from the delegate pattern. However, since much of the code you've already written and all of the code you'll encounter in the iOS standard libraries use it, being able to interact with it is still important.
You'll want to use the -[NSObject rac_signalForSelector:] category, that will return a signal that receives a RACTuple value of the arguments to a method each time it is invoked, and completes when the object sending the signal is deallocated.
Let's say you have a UIViewController to display that contains a list of checkboxes a user can select, with a continue button at the bottom. Since the selections change over time, you could represent it as an RACSignal of NSIndexSet values. For the purposes of this example, let's say you must use this class as is, and it currently declares a delegate pattern that contains the following:
#class BSSelectionListViewController;
#protocol BSSelectionListViewControllerDelegate <NSObject>
- (void)listChangedSelections:(BSSelectionListViewController*)list;
- (void)listContinueTouched:(BSSelectionListViewController*)list;
#end
When you present the view controller from elsewhere (like a UIViewController at the top of the navigation stack), you'll create the view controller and assign self as the delegate. It might look something like
BSSelectionListViewController* listVC = [[BSSelectionListViewController alloc] initWithQuestion:question listChoices:choices selections:idxSet];
listVC.delegate = self;
[self.navigationController pushViewController:listVC];
Before pushing this UIViewController on the stack, you'll want to create signals for the delegate methods that it could call:
RACSignal* continueTouched = [[[self rac_signalForSelector:#selector(listContinueTouched:)]
takeUntil:list.rac_willDeallocSignal]
filter:^BOOL(RACTuple* vcTuple)
{
return vcTuple.first == listVC;
}];
RACSignal* selections = [[[[self rac_signalForSelector:#selector(listChangedSelections:)]
takeUntil:list.rac_willDeallocSignal]
filter:^BOOL(RACTuple* vcTuple)
{
return vcTuple.first == listVC;
}]
map:^id(RACTuple* vcTuple)
{
return [vcTuple.first selections];
}];
You can then subscribe to these signals to do whatever side effects you need. Maybe something like:
RAC(self, firstChoiceSelected) = [selections map:^id(NSIndexSet* selections)
{
return #([selections containsIndex:0]);
}];
and
#weakify(self)
[continueTouched subscribeNext:^(id x)
{
#strongify(self)
[self.navigationController popToViewController:self];
}];
Because it's possible that you might have several of these screens that you're the delegate of, you want to make sure that you are filtering down to just this one in your RACSignals.
ReactiveCocoa will actually implement these methods (the ones in the delegate protocol) for you. However, to keep the compiler happy, you should add stubs.
- (void)listChangedSelections:(BSSelectionListViewController *)list {}
- (void)listContinueTouched:(BSSelectionListViewController*)list {}
This is, IMO, an improvement over the standard delegate pattern, where you would need to declare an instance variable to hold the selection view controller, and check in the delegate methods which controller is calling you. ReactiveCocoa's rac_signalForSelector method can reduce the scope of that state (this view controller comes and goes over time) in to a local variable instead of an instance variable. It also allows you to be explicit about dealing with the changes to the selections.
I am trying to update my UILabel to a String from my Parse database.
My problem is the label will not update my firstnameLabel when I first sign in. But it WILL update, when i sign in (nothing happens), push the stop button in Xcode and then launch it again (still logged in) and then it updates the label.
How can I do this faster??
Here is my code:
var currentUser = PFUser.currentUser()
if currentUser != nil {
var query = PFQuery(className:"_User")
query.getObjectInBackgroundWithId(currentUser.objectId) {
(bruger: PFObject!, error: NSError!) -> Void in
if error == nil && bruger != nil {
var firstName: String = bruger["firstname"] as String
self.usernameLabel.text = firstName
} else {
println("Error")
}
}
} else {
self.performSegueWithIdentifier("goto_login", sender: self)
}
Hope you can help me!
Rather than trying to load the user object again by the id, try just doing a fetch instead.
[currentUser fetchIfNeededInBackgroundWithBlock: ... ];
By trying to load the object, you might be getting a cached version.
I read somewhere that it could be because of the fact that the could was put inside the ViewDidLoad. I tried to put it outside of that and it worked!
It sounds like you put it in the ViewDidLoad method. You should put it in the ViewWillAppear method instead. Here's an example.
1) ViewDidLoad - Whenever I'm adding controls to a view that should appear together with the view, right away, I put it in the ViewDidLoad method. Basically this method is called whenever the view was loaded into memory. So for example, if my view is a form with 3 labels, I would add the labels here; the view will never exist without those forms.
2) ViewWillAppear: I use ViewWillAppear usually just to update the data on the form. So, for the example above, I would use this to actually load the data from my domain into the form. Creation of UIViews is fairly expensive, and you should avoid as much as possible doing that on the ViewWillAppear method, becuase when this gets called, it means that the iPhone is already ready to show the UIView to the user, and anything heavy you do here will impact performance in a very visible manner (like animations being delayed, etc).
3) ViewDidAppear: Finally, I use the ViewDidAppear to start off new threads to things that would take a long time to execute, like for example doing a webservice call to get extra data for the form above.The good thing is that because the view already exists and is being displayed to the user, you can show a nice "Waiting" message to the user while you get the data.