My single view application on XCode has a custom view that is about half the size of the full iPhone screen. I have implemented a drawing tool that is only useable inside of the custom view. The custom view logs the distance the drawing tool was moved. With only one ViewController, I would like to display the distance variable in a Label outside of the custom fiew frame. Do I need to use protocols and delegates to do this? Or is there a much simpler way? I have been testing with the protocols and delegates method for the past few days and have not gotten anywhere.
First in the interface builder add your custom view as a subview of the main view constrollers view, also add the label. Then add outlets for the label and the view to the view controller (let's call them distanceLabel and drawView respectively).
Now declare distanceDrawn as a property of your custom view and when the tool is moved update it to contain the right number.
Then in the view controllers viewDidLoad you add:
[drawView addObersver:self forKeyPath:#"distanceDrawn" options:NSKeyValueObservingOptionNew context:null]
Also add to the controller:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([object isEqual:drawView]) {
id distance = change[NSKeyValueChangeNewKey]; //change to appropriate type
distanceLabel.text = [NSString stringWithFormat:#"%#", distance];
}
}
Now whenever you change distanceDrawn this method will be called and the dictionary called change will contain the newly set value for the key NSKeyValueChangeNewKey. If you retrieve the value you can update the label.
Your custom view should have a property for the drawing length. You can then read the value from any class that has a reference to the custom view.
Probably you also want to have the view controller update the label every time the drawing length increases. You could handle that with Key-Value Observing. You register for notifications from the custom view object when its drawingLength property changes.
You could do this with a delegate. In some cases I think that's a cleaner solution. It's nice to make clear what sort of updates you can listen for from the custom view. However, this kind of case is just what Key-Value Observing is intended for.
Related
I have UI that uses scrollview with paging as well UITableView in one view controller. In order to load UI I am using an array of NSDictionaries (sourceArray) which has necessary information to draw UI. This array is created by consuming RESTful API that returns an array of records. I am returning that array in completion handler. Later I add that array along with some other key:value pairs to create NSDictioary which is later added to sourceArray for UI drawing.
Now my question is how can I update my UI based on changes in sourceArray. I am calling [self.view layoutIfNeeded]; but it does not force redraw.
- (void)getStateDetails {
NSArray *statesArray = [self getStates];
for (NSString *state in statesArray) {
[self getStateDetailsInState:state withCompletionBlock:^(NSArray *records) {
NSLog(#"%#",records);
NSDictionary *dict = #{
#"kImage": state,
#"kName": state,
#"kRecord": records
};
[self.sourceArray addObject:dict];
dispatch_async(dispatch_get_main_queue(), ^{
[self.view layoutIfNeeded];
});
}];
}
}
UI Details-
Viewcontroller contains UIScrollView with various UIViews which used dynamic data and a UITableView that contains dynamic data as well.
This is how I my current UI looks like.
The call to layoutIfNeeded will trigger your views to check to see if they need to update their layouts. Changing the contents of an array will not cause your view's layouts to need updating, so the call to layoutIfNeeded is unlikely to do anything.
In order for us to help you use the dictionary you load to update your UI, you're going to have to tell us what changes you expect the new array element to have on your UI.
One common thing to do is to display the contents of an array in a table view or collectionView. In that case you'd call the table/collection view's reloadData() function, which will cause it to ask it's data source for it's number of entries and re-render all of it's cells.
EDIT
Ok, and what is the relationship between the data you download and your UI? What code have you written that is supposed to cause the UI to update? You've changed your array, but you don't show any code that tells your view objects about updated data.
For a table view, that might be as simple as calling reloadData as mentioned above. For other views, how is your data being displayed into those views? What mechanism is supposed to install new data into those views?
The easiest way to get a view to re-draw itself is to call [UIView setNeedsDisplay] or by using [UIView setNeedsDisplayInRect]. This method tells the view that it needs to re-draw itself and it's subviews. However you shouldn't always have to call this. There are other methods that can cause views to redraw themselves. For instance if you add or remove a view to the view hierarchy using [UIView addSubview:] or [UIView removeFromSuperview].
These methods cause the system to call your view's drawInRect: method (assuming you overrode it with custom drawing code). If you did not override this method for custom drawing then it should cause your subviews to receive the same message so that they can get a chance to redraw.
Additional information here. Specifically read the section labeled "The View Drawing Cycle".
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/uiview/uiview.html
One of the stackoverflow posts where you can add an observer and force call the controller to relaod
How to reload UIViewController
You have to use [tableView reloadData] and then if needed
[self.view layoutSubviews];
[self.view setNeedsDisplay];
[self.view setNeedsLayout];
I'm trying to change the title of a button after I call back from a notification but it doesn't respond at all. I checked it's not nil and checked the text Im' assigning and all is good. I made the property type strong instead of weak but no success.
- (void) setButtonTitleFromSelectedSearchResult:(NSNotification *)notif
{
[self popController];
self.sourceMapItem = [[notif userInfo] valueForKey:#"SelectedResult"];
NSLog(#"The Selected Result is: %#", self.sourceMapItem.name);
//Testing
NSLog(#"%#", self.fromButton); // check it's not nil
[self.fromButton setTitle:self.sourceMapItem.name];
}
With WatchKit, if a user interface element isn't currently visible, it cannot be updated. So, if you've presented another interface controller "on top", you can't update any of the presenting controller's interface elements until you've dismissed the presented controller. At that point, you can safely update the presenting controller in its willActivate method.
SushiGrass' method of passing blocks is certainly one valid approach. In my testing, however, I ended up having to manage multiple blocks, and many of the subsequent blocks reversed what earlier queued blocks had accomplished (for example, first changing a label's text to "foo", then "bar", then "foo" again. While this can work, it isn't optimal.
I'd suggest that anyone who is working on a WatchKit app takes a moment to consider how they want to account for off-screen (i.e. not-currently-visible) interface elements. willActivate is your friend, and coming up with a way to manage updates in that method is worthwhile if you're moving from controller to controller.
For what it's worth, I've encapsulated a lot of this logic in a JBInterfaceController subclass that handles a lot of this for you. By using this as a base class for your own interface controller, you can simply update your elements in the added didUpdateInterface method. Unfortunately, I haven't yet had the time to write proper documentation, but the header files and sample project should get you going: https://github.com/mikeswanson/JBInterfaceController
I'm using latest XCode 6.3 and below code working with me.
self.testBtn is bind with Storyboard and its WKInterfaceButton
I also have attached screenshot with affected result.
I'm setting initial text in - (void)willActivate
- (void)willActivate {
[super willActivate];
[self.testBtn setTitle:#"Test"];
[self performSelector:#selector(justDelayed) withObject:nil afterDelay:5.0]
}
-(void)justDelayed
{
[self.testBtn setTitle:#"Testing completed...!!"];
}
If you're using an IBOutlet for the property fromButton be sure that is connected to WKInteface on the storyboard, like below:
I solved this kind of issue by creating a model object that has a property that is a block of type () -> (Void) (in swift). I create the model object, set the action in the block that I'd like the pushing WKInterfaceController to do on completion, and finally pass that model object in the context to the pushed WKInterfaceController. The pushed WKInterfaceController holds a reference to the model object as a property and calls it's completion block when it's done with whatever it needs to do and after func popController().
This worked for me for patterns like what you are describing along with removing rows on detail controller deletion, network calls, location fetches and other tasks.
You can see what I'm talking about here: https://gist.github.com/jacobvanorder/9bf5ada8a7ce93317170
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 very new to objective-c and programming in general, and just started building a tab-based app in xcode. I have three view controllers and a slider in each of the views. I want the sliders in the second and third view to copy the position (and therefore the value) of the slider in the first view - and vice versa. So that irrespective of which view the user is at, it looks as though there is only one slider throughout.
I’m pretty sure there must be a simpler way to go about this. I hope I’ve explained my issue adequately.
Any advice would be greatly appreciated.
There are a few solutions you can implement.
You can create one slider and have a reference to it in some type of singleton object like the AppDelegate and pass it around to the respective views adding as a subview. Since a view can only have one super view your are safe.
You can use NSNotifications to notify each of the sliders that a value has changed. NSNotifications allow you to pass in an NSDictionary call userInfo so you can pass values around.
My personal opinion is that number 1 is cleaner, NSNotifications are heavy and there might be a slight delay on when things actually get updated.
Add following target to your slider
- (IBAction)sliderValueChanged:(UISlider *)sender
{
// fire notification here
[[NSNotificationCenter defaultCenter] postNotificationName:#"SliderValueChanged" object:nil];
}
and in your other view controllers, you can add observer for "SliderValueChanged"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(onSliderValueChanged:)
name:"SliderValueChanged" object:nil];
- (void)onSliderValueChanged:(NSNotification *)notification
{
// Adjust your respective view controller's slider here
}
Another approach is to store your UISlider value in NSUserDefaults when it changes. Now in every controllers ViewWillAppear method you can get that value from NSUserDefaults and set it for your UISlider. That way no matter which controller the user is looking at it will show the same value for them.
I have some UI that I need to redraw based on changes to an MKMapView when the user pans or zooms the map.
Currently I am using a move event gesture recogniser and MKMapViewDelegate regionDidChangeAnimated messages to redraw my dependant UI. This is 90% of what I need.
The events I am missing are from the point the user lifts their finger (no more move events) to when the MKMapViewDelegate regionDidChangeAnimated message is fired. During that period the map slowly pans to a halt and my dependant UI is stuck with map tile content that is out of synch.
Is there a lower level API that will notify me when UIView (in this case MKMapView) content is redrawn?
Update
I tried creating a proxy MKMapView subclass that forwarded drawRect calls onto my supplied delegate. I get the first draw event but none of the subsequent ones, so this doesn't help with my predicament.
IIRC, MKMapView is unfortunately not KVO-compliant for #"region".
You might hack you way setting up an NSTimer in regionWillChangeAnimated, using it to refresh you UI during the scroll/pan/etc, and discarding it in regionDidChangeAnimated. Not quite elegant though, and it may not suit your needs if you need to be really fast.
Alternatively, you may look for the UIScrollView in MKMapView's subviews, and observe its transform.
(I haven't tested any of these.)
In any case, I doubt that monitoring redraw events on a MKMapView will be of any use : MKMapView uses CATiledLayer to perform its drawing asynchronously, so you can't even be sure when it's done.
Edit : This apparently does not work with iOS 6. Of course, this should not really come as a surprise. However, as far as I know, the delegate methods behave the same, so the OP's problem is still real. I haven't looked for an updated solution.
Hate to post THESE kind of solutions, but.
MKMapView has many subview in itself.
In it's subviews hierarchy there's an view with class MKTiledView, which have TiledLayer as layer.
So, actually, you can't resolve notifications of rendering in "normal" way.
Tiled layer renders it's contents by constantly calling -drawLayer:inContext: method of it's delegate, which MKTiledView is. Those calls can be performed simultaneosly in different threads.
You're not receiving norifications(updates) from MKMapView because it isn't updating itself. Only underlying contents of it are updating.
So. There's always better solution exists.
My solution depends on view hierarchy and method's swizzling.
It's up to you, to use it or not.
Creating category-method in which we will post "update notifications" to custom view that need to be updated
#implementation UIView (Custom)
- (void)drawCustomLayer:(CALayer *)layer inContext:(CGContextRef)ctx {
NSLog(#"Need to draw custom layer :%# in context %#, Thread: %#", layer, ctx, [NSThread currentThread]);
// Calling old method
[self drawCustomLayer:layer inContext:ctx];
}
#end
// Exchanging method implementation of MKTiledView to our custom implementation
#import <objc/runtime.h>
Class tileViewclass = NSClassFromString(#"MKMapTileView");
Class viewClass = NSClassFromString(#"UIView");
SEL originalSelector = #selector(drawLayer:inContext:);
SEL newSelector = #selector(drawCustomLayer:inContext:);
Method origMethod = class_getInstanceMethod(tileViewclass, originalSelector);
Method newMethod = class_getInstanceMethod(viewClass, newSelector);
method_exchangeImplementations(origMethod, newMethod);
Still looking for better solution.
MKMapView has many subviews that redraws. It is very hard to find which view or layer drawed...
Alternatively you could try to find some of MKMapView properties are changed. You can do this with Key Value Observing (KVO) mechanics. http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
Eg. (properties can be changed to whatever you need)
[myMapView addObserver:self forKeyPath:#"region" options:NSKeyValueObservingOptionNew context:nil];
[myMapView addObserver:self forKeyPath:#"centerCoordinate" options:NSKeyValueObservingOptionNew context:nil];
And you should implement observeValueForKeyPath:ofObject:change:context:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
// Do something that you want with keyPath;
}
Whenever your mapView has new values for each property you defined, this method will be fired.