Reputation: 356
I am a newbie to iOS development. I have gone through a couple of tutorials and know the basics, but currently I am stuck on how to proceed further. I am planning to create an app for basic home automation (i.e. switching lights, measuring temperature etc.). The backend is all set, so this is just about the frontend. This is what I am planning to do:
I know there is a lot of work to do, but I don't really know how to set this up. Current alternatives:
Or am I totally on the wrong track? Any input is appreciated.
Thanks D.
UPDATE: After some more research and thought on this I think the way to go with iOS 6 is to use an UICollectionView with a custom layout. Once I have come up with a complete solution I will post it here. For older iOS versions I think it would be promising to go with Option Nr. 2 - i.e. creating each UIButton (for the automation objects e.g. lights) in code and having a custom UIView subclass to do the layouting of these buttons.
Upvotes: 2
Views: 893
Reputation: 356
Ok I think UICollectionView is ideal for this usage scenario and I am just lucky to have started with iOS programming just as it was introduced to the framework. The following example is a UICollectionView that displays its elements according to their inherent coordinates. This example could also be applied to positioning objects on a map. I couldn't find any examples elsewhere so I'll post the main steps here (since I am a beginner please correct any mistakes).
To start off I created a simple project with one view and storyboard in XCode. I removed the standard view and inserted a Collection View Controller instead and configured my UICollectionViewController subclass as the class that should be used (in the properties of the controller in storyboard).
For the demo just set the background of the default UICollectionViewCell to a color and set the Identifier to "AutomationCell" for this example (if you change it be sure to adjust the code below).
First I create a simple object with some properties that represents an object that should be displayed on the floor plan:
@interface AULYAutomationObject : NSObject
@property NSString *title;
@property CGPoint position;
@end
Then I need my own delegate as subclass to the standard UICollectionViewDelegate since my custom UICollectionViewLayout will not have direct access to the dataSource objects. Therefore I provide a method that will give me the position of the object:
@protocol AULYAutomationObjectLayoutDelegate <UICollectionViewDelegate>
- (CGPoint)getPositionForItemAtIndexPath:(NSIndexPath *)indexPath;
@end
Make sure to implement this protocol in your controller like this:
@interface AULYViewController : UICollectionViewController <AULYAutomationObjectLayoutDelegate>
Then I implemented the standard datasource and delegate methods along with my custom one in the view controller subclass:
@interface AULYViewController ()
@property NSArray *objects;
@property (strong, nonatomic) IBOutlet UICollectionView *collectionView;
@end
@implementation AULYViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Set up the data source
NSMutableArray *automationObjects = [[NSMutableArray alloc] initWithCapacity:10];
// add some objects here...
self.objects = [automationObjects copy];
UILongPressGestureRecognizer *longPressRecognizer = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
[self.collectionView addGestureRecognizer:longPressRecognizer];
}
#pragma mark - UICollectionViewController
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.objects.count;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
AULYAutomationObjectViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"AutomationCell" forIndexPath:indexPath];
// If you have a custom UICollectionViewCell with a label as outlet
// you could for example then do this:
// AULYAutomationObject *automationObject = self.objects[indexPath.row];
// cell.label.text = automationObject.title;
return cell;
}
#pragma mark - AULYAutomationObjectLayoutDelegate
- (CGPoint)getPositionForItemAtIndexPath:(NSIndexPath *)indexPath
{
AULYAutomationObject *automationObject = self.objects[indexPath.item];
return automationObject.position;
}
In a real project you would probably do some conversion from the object model position to the position on screen (e.g. GPS data to pixels) but here this is left out for simplicity.
After having done that we still need to set up our layout. This has the following properties:
@interface AULYAutomationObjectLayout : UICollectionViewLayout
@property (nonatomic, strong) NSIndexPath *draggedObject;
@property (nonatomic) CGPoint dragPosition;
@end
And the following implementation:
@implementation AULYAutomationObjectLayout
- (void)setDraggedObject:(NSIndexPath *)draggedObject
{
_draggedObject = draggedObject;
[self invalidateLayout];
}
- (void)setDragPosition:(CGPoint)dragPosition
{
_dragPosition = dragPosition;
[self invalidateLayout];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewLayoutAttributes *layoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
id viewDelegate = self.collectionView.delegate;
if ([viewDelegate respondsToSelector:@selector(getPositionForItemAtIndexPath:)])
{
CGPoint itemPosition = [viewDelegate getPositionForItemAtIndexPath:indexPath];
layoutAttributes.center = itemPosition;
layoutAttributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
}
if ([self.draggedObject isEqual:indexPath])
{
layoutAttributes.center = self.dragPosition;
layoutAttributes.transform3D = CATransform3DMakeScale(1.5, 1.5, 1.0);
layoutAttributes.zIndex = 1;
}
return layoutAttributes;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *allAttributes = [[NSMutableArray alloc] initWithCapacity:4];
for (NSInteger i = 0; i < [self.collectionView numberOfItemsInSection:0]; i++)
{
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *layoutAttributes = [self layoutAttributesForItemAtIndexPath:indexPath];
[allAttributes addObject:layoutAttributes];
}
return allAttributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return YES;
}
- (CGSize)collectionViewContentSize
{
return [self.collectionView frame].size;
}
@end
To set the custom layout in the storyboard just go to the properties of the controller view and select custom as the layout type - then select your custom class.
Now to enable drag and drop support with the long press gesture simply add the following to your controller:
- (void)handleTapGesture:(UITapGestureRecognizer *)sender
{
AULYAutomationObjectLayout *automationLayout = (AULYAutomationObjectLayout *)self.collectionView.collectionViewLayout;
if (sender.state == UIGestureRecognizerStateBegan)
{
CGPoint initialPinchPoint = [sender locationInView:self.collectionView];
NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint];
[self.collectionView performBatchUpdates:^{
automationLayout.draggedObject = tappedCellPath;
automationLayout.dragPosition = initialPinchPoint;
} completion:nil];
}
else if (sender.state == UIGestureRecognizerStateChanged)
{
automationLayout.dragPosition = [sender locationInView:self.collectionView];
}
else if (sender.state == UIGestureRecognizerStateEnded)
{
AULYAutomationObject *automationObject = self.objects[automationLayout.draggedObject.item];
automationObject.position = [sender locationInView:self.collectionView];
[self.collectionView performBatchUpdates:^{
automationLayout.draggedObject = nil;
automationLayout.dragPosition = CGPointMake(0.0, 0.0);
} completion:nil];
}
}
One important note:(this cost me at least an hour): When using the transform3D you should make sure to import QuartzCore into your linked frameworks (in the project properties below the orientation settings). Otherwise you will get a Mach-O Linker Error saying that _CATransform3DMakeScale can not be found.
Upvotes: 3