How to implement custom UIDynamicBehavior action - objective-c

I have been looking for an example showing how to implement a custom UIDynamicBehavior in UIKit dynamics. All the tutorials and examples show only how to assemble a UIDynamicBehavior using primitives (collision, gravity, attachment, push, snap etc.)
In my app, some views are floating around the screen (using dynamics) and I want to make them disappear when they overlap other stationary views. To do this, I wanted to test for overlap in the UIDynamicAnimator and UICollisionBehavior delegate methods, but unfortunately those methods do not provide enough granularity to get perform the tests I need.
EDIT: apparently I had to wait a day before answering my own question (new user), so my solution is posted below as an answer now.

The approach I chose was to develop my own UIDynamicBehavior class and add that to the animator, and it now makes the floating views disappear when they overlap the stationary views.
Sample code below shows how to write your own UIDynamicBehavior class to plug your own behaviour into the UIDynamicAnimator. I called the class UISinkBehavior, because it "sinks" a view when the view moves over the "sinkhole".
// UISinkBehavior.h
#import <UIKit/UIKit.h>
#protocol UISinkBehaviorDelegate <NSObject>
- (void)sunk:(id)item;
#end
#interface UISinkBehavior : UIDynamicBehavior
#property (weak, nonatomic) id<UISinkBehaviorDelegate> delegate;
- (id)initWithItems:(NSMutableArray*)items withSinkhole:(UIView*)sinkhole;
#end
// UISinkBehavior.m
#import "UISinkBehavior.h"
#interface UISinkBehavior ()
#property (nonatomic) NSMutableArray *items;
#property (nonatomic) id<UIDynamicItem> sinkhole;
#end
#implementation UISinkBehavior
- (id)initWithItems:(NSMutableArray*)items withSinkhole:(UIView*)sinkhole
{
if (self = [super init])
{
_items = items;
_sinkhole = sinkhole;
// weak self ref to avoids compiler warning about retain cycles
__weak typeof(self) ref = self;
// this is called by the UIDynamicAnimator on every tick
self.action = ^{
UIView *item;
// check each item if it overlaps sinkhole
for (item in ref.items)
if (CGRectIntersectsRect(item.frame, sinkhole.frame))
{
// sink it (handled by delegate
[ref.delegate sunk:item];
// remove item from animation
[ref.items removeObject:item];
// remove behaviour from animator when last item sunk
if (ref.items.count < 1)
[ref.dynamicAnimator removeBehavior:ref];
}
};
}
return self;
}
#end

Related

Passing Data from view Controller to NSObject class

I know how to pass the data from one view controller to another view controller ,now i want to know how to pass a textfield value from view controller to NSObject class and how to store the recieved in nstring .Please help me to do this , Can anyone give a example for this ,
I think what you're asking is how to store data in a model object for use by your view controller. If this is not your meaning, then please forgive me.
You are right that a model object should inherit from NSObject. Optionally, you could also extend another model object to add property values. Model objects are a great way to separate the view objects from your data.
Let's say you have a CustomerViewController with some customer fields. You need to populate those customer fields, and potentially perform some processing on that data. The model object supports these relationships, by allowing you to separate your views from any processing logic related to your data and business rules.
Using the relationships below as a guide, you should be on your way to building effective view controllers, that separate your views from your data!
CustomModel Interface
#interface CustomerModel : NSObject
#property (strong, nonatomic) NSString *firstName, *lastName;
#property (strong, nonatomic) NSString *phoneNumber;
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber;
#end
CustomerModel Implementation
#import "CustomerModel.h"
#implementation CustomerModel
- (BOOL) isValidPhoneNumber:(NSString *)phoneNumber
{
//Check that phone number can be parsed and is valid
}
#end
CustomerViewController Implementation
#import "CustomerViewController.h"
#import "CustomerModel.h"
#interface CustomerViewController () <UITextFieldDelegate>
#property (strong, nonatomic) CustomerModel *customerModel;
...
#property (weak, nonatomic) IBOutlet UITextField *firstNameField
...
#end
#implementation CustomerViewController
- (void) viewDidLoad {
//Optionally instantiate the customer model with stored data,
// to pre-populate the view controller.
self.customerModel = [CustomerModel new];
self.firstNameField.delegate = self;
}
- (void) textFieldDidEndEditing:(UITextField *)textField {
//Validate the phone number
NSString *phoneNumber = textField.text;
if ([self.customerModel isValidPhoneNumber:phoneNumber]) {
self.customerModel.phoneNumber = phoneNumber;
} else {
//Alert the user that the data is invalid
}
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
return YES;
}
#end
Possible improvements
It might be a little annoying to the User, to see alerts while filling out information. So, it might be better to defer the validation to when a save button is pressed.
Model objects can be populated from a data store, to be used to pre-populate a form.

How to initialize main window objects from custom class during program start?

I have a main window with a couple of popupbuttons. I want to clear them, then load the lists from a method in a custom class. I've got my view controller working and I know the method in the custom class (newRequest) is working because I added a NSLog command to print "Test" when the method executes. In AppDelegate I'm calling the method via:
[polyAppRequest newRequest];.
As I said, I know the method is executing. Why can't I removeallitems from the popupbutton from this custom class method?
Thanks
Keith
I read that you should use an NSWindowController to manage a window. See here:
Windows and window controllers
Adding views or windows to MainWindow
Then if your window gets complicated enough, the NSWindowController can employ various NSViewControllers to manage parts of the window.
In any case, I used an NSWindowController in my answer.
The image below shows the outlet's for File's Owner, which is my MainWindowController:
I created MainWindowController .h/.m in Xcode6.2 by:
Selecting File>New>File>OS X - Source - Cocoa Class
Selecting NSWindowController for Subclass of:
Checking also create .xib file for user interface
Then I deleted the window--not the menu--in the default MainMenu.xib, and I changed the name of MainWindowController.xib, created by the steps above, to MainWindow.xib.
The following code works for me (but I'm a Cocoa beginner!):
//
// AppDelegate.m
// PopUpButtons
#import "AppDelegate.h"
#import "MainWindowController.h"
#interface AppDelegate ()
#property(strong) MainWindowController* mainWindowCtrl;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[self setMainWindowCtrl:[[MainWindowController alloc] init]];
[[self mainWindowCtrl] showWindow:nil];
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
#end
...
//
// MainWindowController.m
// PopUpButtons
//
#import "MainWindowController.h"
#import "MyData.h"
#interface MainWindowController ()
#property(strong) MyData* data;
#property(weak) IBOutlet NSPopUpButton* namePopUp;
#property(weak) IBOutlet NSPopUpButton* agePopUp;
#end
#implementation MainWindowController
-(id)init {
if (self = [super initWithWindowNibName:#"MainWindow"]) {
_data = [[MyData alloc] init]; //Get data for popups
}
return self;
}
- (void)windowDidLoad {
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
[[self namePopUp] removeAllItems];
[[self namePopUp] addItemsWithTitles:[[self data] drinks]];
[[self agePopUp] removeAllItems];
[[self agePopUp] addItemsWithTitles:[[self data] extras]];
}
#end
...
//
// MyData.h
// PopUpButtons
//
#import <Foundation/Foundation.h>
#interface MyData : NSObject
#property NSArray* drinks;
#property NSArray* extras;
#end
...
//
// MyData.m
// PopUpButtons
//
#import "MyData.h"
#implementation MyData
- (id)init {
if (self = [super init]) {
_drinks = #[#"coffee", #"tea"];
_extras = #[#"milk", #"sugar", #"honey"];
}
return self;
}
#end
I hope that helps. If you need any more screenshots, let me know.
Edit1:
I think I see what you are asking about. Although I don't think it is a very good approach, if I change my code to this:
//
// MyData.h
// PopUpButtons
//
#import <Cocoa/Cocoa.h>
#interface MyData : NSObject
#property (copy) NSArray* drinks;
#property (copy) NSArray* extras;
-(void)newRequest;
#end
...
//
// MyData.m
// PopUpButtons
//
#import "MyData.h"
#interface MyData()
#property (weak) IBOutlet NSPopUpButton* drinksPopUp;
#property (weak) IBOutlet NSPopUpButton* extrasPopUp;
#end
#implementation MyData
- (id)init {
if (self = [super init]) {
_drinks = #[#"coffee", #"tea"];
_extras = #[#"milk", #"sugar", #"honey"];
}
return self;
}
-(void)newRequest {
[[self drinksPopUp] removeAllItems];
[[self drinksPopUp] addItemsWithTitles:[self drinks]];
[[self extrasPopUp] removeAllItems];
[[self extrasPopUp] addItemsWithTitles:[self extras]];
}
#end
I am unable to populate the NSPopUpButtons. This is what I did:
I dragged an Object from the Object Library to the dock in IB, and in the Identity Inspector, I changed the Object's class to MyData.
Then I clicked on the Connections Inspector, and the two instance variables in MyData, drinksPopUp and extrasPopUp, were listed in the Outlets.
I dragged from the outlets to the respective NSPopUpButtons.
I guess I assumed, like you, that when my program ran, the NSPopUpButtons would be assigned to the instance variables drinksPopUp and extrasPopUp--but that doesn't seem to be the case. According to the Apple docs, you should be able to do that:
An application typically sets outlet connections between its custom
controller objects and objects on the user interface, but they can be
made between any objects that can be represented as instances in
Interface Builder,...
Edit2:
I am able to pass the NSPopUpButtons from my MainWindowController to the newRequest method, and I can use the NSPopUpButtons inside newRequest to successfully populate the data.
Edit3:
I know the method in the custom class (newRequest) is working because
I added a NSLog command to print "Test" when the method executes.
But what happens when you log the variables that point to the NSPopUpButtons? With my code in Edit1, I get NULL for the variables, which means the NSPopUpButtons never got assigned to the variables.
Edit4:
If I add an awakeFromNib method to MyData, and inside awakeFromNib I log the NSPopUpButton variables for the code in Edit1, I get non NULL values. That tells me that the MainWindowController's windowDidLoad method is executing before MyData's awakeFromNib method, and therefore you cannot call newRequest inside MainWindowController's windowDidLoad method because MyData has not been fully initialized.
Edit5:
Okay, I got the code in Edit1 to work. The Apple docs say this:
About the Top-Level Objects
When your program loads a nib file, Cocoa recreates the entire graph
of objects you created in Xcode. This object graph includes all of the
windows, views, controls, cells, menus, and custom objects found in
the nib file. The top-level objects are the subset of these objects
that do not have a parent object [in IB]. The top-level objects typically
include only the windows, menubars, and custom controller objects that
you add to the nib file [like the MyData Object]. (Objects such as File’s Owner, First
Responder, and Application are placeholder objects and not considered
top-level objects.)
Typically, you use outlets in the File’s Owner object to store
references to the top-level objects of a nib file. If you do not use
outlets, however, you can retrieve the top-level objects from the
nib-loading routines directly. You should always keep a pointer to
these objects somewhere because your application is responsible for
releasing them when it is done using them. For more information about
the nib object behavior at load time, see Managing the Lifetimes of
Objects from Nib Files.
In accordance with the bolded line above, I changed this declaration in MainWindowController.m:
#interface MainWindowController ()
#property(strong) MyData* data;
...
#end
to this:
#interface MainWindowController ()
#property(strong) IBOutlet MyData* data;
...
#end
Then, in IB I dragged a connection from the MainWindowController data outlet to the MyData Object(the Object I had previously dragged out of the Object Library and onto the doc).
I guess that causes MyData to unarchive from the .xib file and initialize before MainWindowController.

accessing superview properties

I've searched for the answer to this question, and the answers I'm finding don't work.
I have a view that is a subclass of UIView to which I've added a property. I would like to access this property from the subviews created by this view. Is that possible?
I've tried referring to self.superview.propertyname but I get an error that propertyname is not found on object of type UIView. Well, right. I realize that since it's a subclass of UIView, it's a UIView, but how can I get it to know about the extra property I added?
You have a number of options, two of them are:
1. Casting:
#implementation SubviewView
- (void)blah
{
((CustomView *)self.superview).property = ...`
}
#end
2. Delegates:
#protocol SubviewViewDelegate
- (void)customView:(SubView *)sv modified:(...)value;
#end
#class SubView
#property (nonatomic, weak) id <CustomViewDelegate> delegate;
#end
#implementation SubviewView
- (void)blah
{
[self.delegate subView modified:...];
}
#end
#implementation CustomView
- (void)subView:(SubView *)sv modified:(...)value
{
self.property = value;
}
#end
Although the second option is more code, I think it is often better suited. Using delegates reduces coupling and works nicely with the Law of Demeter. For more info see this documentation.

How to generate a generic table view controller?

I've created a custom TablePickerViewController which is a subclass of UITableViewController. I'm using this class to display a list of object of a custom type TablePickerItem.
I'm using TablePickerViewController multiple times in my iOS application to show different kinds of lists where the user has to pick an item -- and then another view controller MainViewController should react on this selection and do something.
I've created this protocol and created a delegate property in the TablePickerViewController:
#protocol TablePickerViewControllerDelegate <NSObject>
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
#end
When I setup a new TablePickerViewController in MainViewController it is also set as delegate -- than it will be notified when the user taps an cell in the table view.
The problem is that my MainViewController will setup multiple TablePickerViewController with different data (TablePickerItem). How should I setup my MainViewController to handle these multiple TablePickerViewController? Events from each of them will results in calling to the same protocol-method in my MainViewController.
Further I need to get the element which the TablePickerItem represents, as I need to know for instance the elements ID when acting in the tablePickerViewController:didSelectItem method. Should I just handle this by adding something like #property (nonatomic) id element to the TablePickerItem and set the original object into this property then creating it?
Maybe someone can give me an example on how to create an generic table view controller, if my solutions seems being done in the wrong way.
I'm not entirely sure of your set up, but if you have multiple pickers that feedback to the main controller then you could just have a reference to the picker e.g.
// MainViewController.m
#interface MainViewController ()
#property (nonatomic, strong) TablePickerViewController *picker1;
#property (nonatomic, strong) TablePickerViewController *picker2;
// ... and so on. Obviously you know your problem domain so you can change
// the terrible naming above to something appropriate
#end
#implementation MainViewController
// ...
- (void)theMethodWhereYouSetUpYourPickers;
{
TablePickerViewController *picker1 = [[TablePickerViewController alloc] init];
picker1.delegate = self;
self.picker1 = picker1;
// ...
}
- (void)tablePickerViewController:(TablePickerViewController *)controller
didSelectItem:(TablePickerItem*)item;
{
if (controller == self.picker1) {
NSLog(#"Something was picked in picker 1 %#", item);
} else if (controller == self.picker2) {
NSLog(#"Something was picked in picker 2 %#", item);
}
}
// ...
#end

No access to global instance (build by factory) on iOS

this is a follow-up question to my last one here: iOS: Initialise object at start of application for all controllers to use .
I have set my application up as follows (ignore the DB Prefix):
DBFactoryClass // Built a DataManaging Object for later use in the app
DBDataModel // Is created by the factory, holds all data & access methods
DBViewControllerA // Will show some of the data that DBDataModel holds
moreViewControllers that will need access to the same DBDataModel Object
i will go step by step through the application, and then post the problem in the end
AppDelegate.h
#import "DBFactoryClass.h"
AppDelegate.m
- (BOOL)...didFinishLaunching...
{
DBFactoryClass *FACTORY = [[DBFactoryClass alloc ]init ];
return YES;
}
DBFactoryClass.h
#import <Foundation/Foundation.h>
#import "DBDataModel.h"
#interface DBFactoryClass : NSObject
#property (strong) DBDataModel *DATAMODEL;
#end
DBFactoryClass.m
#import "DBFactoryClass.h"
#implementation DBFactoryClass
#synthesize DATAMODEL;
-(id)init{
self = [super init];
[self setDATAMODEL:[[DBDataModel alloc]init ]];
return self;
}
#end
ViewControllerA.h
#import <UIKit/UIKit.h>
#import "DBDataModel.h"
#class DBDataModel;
#interface todayViewController : UIViewController
#property (strong)DBDataModel *DATAMODEL;
#property (weak, nonatomic) IBOutlet UILabel *testLabel;
#end
ViewControllerA.m
#import "todayViewController.h"
#implementation todayViewController
#synthesize testLabel;
#synthesize DATAMODEL;
- (void)viewDidLoad
{
todaySpentLabel.text = [[DATAMODEL test]stringValue]; // read testdata
}
#end
DBDataModel.h
#import <Foundation/Foundation.h>
#interface DBDataModel : NSObject
#property (nonatomic, retain) NSNumber* test;
#end
DBDataModel.m
#import "DBDataModel.h"
#implementation DBDataModel
#synthesize test;
-(id)init{
test = [[NSNumber alloc]initWithInt:4]; // only a testvalue
return self;
}
#end
the app builds fine, and starts up but the label stays blank. so either the object does not exist (but i guess this would result in an error message), or something else is wrong with my setup. any thoughts?
Two notes:
Your have a shotgun approach to asking questions: everytime you hit a stumbling block, you ask a question and if the answer does not work immediately, you ask another one. You have to spend some energy in between the questions debugging and poking into the code on your own, otherwise you will depend on the external help forever.
Use the common coding style please. CAPS are reserved for macros.
Now to the code:
- (BOOL) …didFinishLaunching…
{
DBFactoryClass *factory = [[DBFactoryClass alloc] init];
return YES;
}
This simply creates an instance of the DBFactoryClass and then throws it away. In other words, it’s essentially a no-op. Judging by the comments in the previous answer you create the controllers using the Storyboard feature. How are they supposed to receive the reference to the data model? The reference isn’t going to show up by magic, you have to assign it somewhere.
I’m not familiar with the Storyboard feature. The way I would do it is to create the view controllers using separate XIB files, then you can create the controller instances in the Factory class and pass them the needed reference to the model. In the end the application delegate would create the factory, ask it to assemble the main controller and then set it as the root view controller for the window. Just like in my sample project. It’s possible that there’s a way to make it work with storyboards, but as I said, I am not familiar with them.