I've overwritten the subclass and it refuses to be call touchesShouldCancelInContentView. I swear I've written code to do this before. I'm overwriting the class so that certain views don't pass along the touch events to the scroll view.
With the class
#interface SlidingWindowScrollView : UIScrollView
{
UIView* noTouchView;
}
#property (nonatomic, retain) UIView* noTouchView;
#end
And the implementation
#implementation SlidingWindowScrollView
#synthesize noTouchView;
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
BOOL shouldCancel = NO;
if(view == noTouchView)
shouldCancel = YES;
NSLog(#"shouldCancel %d", shouldCancel);
return shouldCancel;
}
#end
In the xib I throw down a scrollview and a uiview inside it and write this in the VC view did load.
scrollView.contentSize = CGSizeMake(scrollView.bounds.size.width*2,
scrollView.bounds.size.height);
scrollView.pagingEnabled = YES;
scrollView.canCancelContentTouches = YES;
contentView.frame = CGRectMake(scrollView.frame.size.width + 20,
0,
scrollView.frame.size.width,
scrollView.frame.size.height);
contentView.userInteractionEnabled = YES;
scrollView.noTouchView = contentView;
[scrollView setContentOffset:CGPointMake(scrollView.bounds.size.width, 0) animated:YES];
I don't know what gives. This should be a simple override. I made sure that the custom class was correct in the XIB and that the outlets all said it was my new subclass of UIScrollView. I don't mean to be presumptuous but did something break in the newest iOS API? This seems like it should be easy yet it never calls the function touchesShouldCancelInContentView. I'm baffled. I'd greatly appreciate it someone could prove me wrong and get this function to be called by the scrollView. I suppose it should be noted that the print statement did print but it could be consistently done
If you can get this working on your machine please let me know, because I don't see anything wrong. It's driving me nuts #_# Hope someone can help me out. Thanks.
From what I've seen, you don't get touchesShouldCancelInContentView: or touchesShouldBegin:withEvent:inContentView: callbacks unless you have delaysContentTouches set to NO:
scrollView.delaysContentTouches = NO;
I got stuck on this for a while incase you ever read this, you need to have both the following properties set before touches in touchesShouldCancelInContentView will fire
scrollView.delaysContentTouches = NO;
scrollView.canCancelContentTouches = YES;
Just having scrollView.delaysContentTouches = NO wont fire if scrollView.canCancelContentTouches = NO
Related
I have my NSButton layer backed because I wanted to use a custom image, but this seems like it's inhibiting the use of the setFont: method when I need to programmatically change the font, as when I comment out the code for wantsUpdateLayer: and updateLayer:, setFont: works, but when the layer methods are in the code, it does nothing.
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
self.fontChangeButton = [[CustomButton alloc]initWithFrame:NSMakeRect(82, 60, 190, 113)];
[self.window.contentView addSubview:self.fontChangeButton];
}
- (IBAction)changeFont:(id)sender {
[self.fontChangeButton fontChange];
}
#end
#implementation CustomButton
- (void)fontChange{
[self setFont:[NSFont fontWithName:#"Dosis Bold" size:40]];
}
//when these are commented out, setFont: works, but I need them in for the custom button images
- (BOOL)wantsUpdateLayer{
return YES;
}
- (void)updateLayer{
if (self.state == NSOnState) {
self.layer.contents = [NSImage imageNamed:#"buttonPressed.png"];
}else
self.layer.contents = [NSImage imageNamed:#"buttonUnpressed.png"];
}
This thread offers a workaround, but I'd much rather understand why this is happening and fix it: Can't Change NSButton Font
By overriding -wantsUpdateLayer to return YES you're bypassing calls to -drawRect:. This facility was introduced in 10.8 and exists for efficiency purposes.
There are two things I think should be clarified:
1 - You don't need to override -wantsUpdateLayer to be layer-backed. Just send -setWantsLayer:YES to your button to be layer-backed.
2 - In your example, creating a custom NSButtonCell class might be a better approach to what you're trying to do. Have a look at Apple's documentation on subclassing NSControl and this how to to get started.
I am hoping to "automate" a click on the segmentController with the index of 0.
My tabBar-based app has multiple segmentControllers in a tab in the ViewDidAppear method, I would like to automatically have it "click" the first segmented controller.
if (segmentController.selectedSegmentIndex == 0) {
//stuff here
}
if (segmentController.selectedSegmentIndex == 1) {
//stuff here
}
Does anyone know how I might accomplish this? Thank you!
If you're creating it programmatically, you could lazy load it like this:
#interface ExampleViewController : UIViewController
#property (nonatomic, strong) UISegmentedControl *segmentedControl;
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl;
#end
#implementation ExampleViewController
- (UISegmentedControl *)segmentedControl
{
if (!_segmentedControl)
{
NSArray *items = #[#"First", #"Second", #"Third"];
_segmentedControl = [[UISegmentedControl alloc] initWithItems:items];
[_segmentedControl addTarget:self
action:#selector(segmentedControlClicked:)
forControlEvents:UIControlEventValueChanged];
[_segmentedControl setSelectedSegmentIndex:0]; // Set Default selection
CGRect frame = _segmentedControl.frame;
frame.origin = CGPointMake(0.0f, 0.0f); // Move to wherever you need it
[self.view addSubview:_segmentedControl];
}
return _segmentedControl;
}
- (void)segmentedControlClicked:(UISegmentedControl *)segmentedControl
{
// Whatever your code is goes here...
}
#end
If you're wanting a method to be called also initially, you can call it within your viewDidLoad: method as such:
- (void)viewDidLoad
{
[self.segmentedControl setSelectedSegmentIndex:0]; // Set desired default index (optional if set in lazy load as shown above)
[self segmentedControlClicked:self.segmentedControl];
}
This would hence simulate a click on desired default index.
Be careful putting the above into viewDidAppear: (you could if you really wanted to) because anytime the view comes to the front, this method will be called (in example, if this view controller presents a modal view controller, once the modal is dismissed, this view controller's viewDidAppear: method will be called).
Cheers!
Set the selectedSegmentIndex property on your UISegmentedControl in your viewDidAppear (or viewDidLoad) method.
self.segmentedController.selectedSegemntIndex = 1;
UISegmentedControl Reference
In a controller I'm creating a UIScrollView. I'm trying to set this viewcontroller as the UISCrollview delegate and to implement the delegate's method in order to add (later) a UIPageControl.
I've read a bit, and found this link, this other link and other here on SO, and some useful tutorial all around the web, but I don't understand what I'm doing wrong. Everytime a scroll the UIScrollView, the app crashes with an EXC_BAD_ACCESS error.
Here's my .h file
#import <UIKit/UIKit.h>
#interface StatsViewController : UIViewController <UIScrollViewDelegate> {
UIScrollView *scrollView;
UIPageControl *pageControl;
}
#end
Then in my .m file, I'm creating the scrollview and trying to define the delegate method like this:
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger boxWidth = self.view.frame.size.width;
NSInteger boxHeight = 412;
scrollView = [ [UIScrollView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, boxHeight)];
scrollView.pagingEnabled = TRUE;
scrollView.delegate = self;
NSInteger numberOfViews = 2;
StatBreatheCounter *breatheCounter = [ [StatBreatheCounter alloc] init];
breatheCounter.view.frame = CGRectMake(0, 0, boxWidth, boxHeight);
[scrollView addSubview:breatheCounter.view];
BreatheLocationViewController *breatheLocation = [ [BreatheLocationViewController alloc] init];
breatheLocation.view.frame = CGRectMake(320, 0, boxWidth, boxHeight);
[scrollView addSubview:breatheLocation.view];
scrollView.contentSize = CGSizeMake(self.view.frame.size.width * numberOfViews, boxHeight);
[self.view addSubview:scrollView];
}
- (void)scrollViewDidScroll:(UIScrollView *)sender {
NSLog(#"RUNNING");
}
...but every time I slide on the scroll view, the app is crashing.
Now, I'm quite a n00b on Ojective-C, but I feel I'm missing something. Browsing around everything points on the fact that the delegate could be deallocated early, and when the user trigger the action, no one is handling the method (sorry for the explanation :)).
...but if the delegate it's the viewcontroller itself, how could it be deallocated?
As you can see, I'm quite confused :(
Any help would be really appreciated
--
EDIT:
I'm going to include here the solution founded thanks with your comments and answer.
When I posted my question I was so convinced that the error was coming from the way I was creating the UIScrollView and setting its delegate that I didn't realize that the problem was (as everything was suggesting, btw :)) I was allocating the StateViewController in its parent without declaring any "strong" reference to it (again, sorry for the explanation, I'm really a n00b in this).
Thanks a lot for your helping in pointing me on the right direction
It looks like you are losing reference to the delegate during scroll. I would look into any other release events around StatsViewController or other events that could cause it to be dereferenced.
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:
The main view of the app should display a floor plan or the layout of the house
On this floor plan you should be able to add lights/sensors/etc. - lets say objects to keep it generic
These objects should be draggable so that you can arrange them on the floor plan according to where they really are (physically) - ideally this drag mode is toggable similar to rearranging icons on the home screen
Each object should have a popover view (i.e. to set the dimmer intensity, switch lights etc.)
I know there is a lot of work to do, but I don't really know how to set this up. Current alternatives:
Create a custom UIView subclass that contains all the logic an do the drawing in custom code, i.e. the dragging, the popover positioning etc. - but I have the feeling that I wouldn't really be leveraging the iOS framework capabilities
Display the floor plan as an UIImageView and one UIButton for each object. This has the advantage that I can use StoryBoard to do the layouting and wiring (i.e. create segues for popovers etc.) - but I simply can't figure out how to do this with a variable number of buttons (since I don't know in advance how many buttons there will be). Is there some way to create these buttons in code?
Use a custom UITableView. I have seen a couple of examples where they seem to use table views even if the layout has nothing to do with tables (like in my example) but I haven't found any tutorials that explain this concept in more detail
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.
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.
I'm working on a custom implementation of UISegmentedControl.
I'd like to create a component that able to receive config data and from which obtain a custom View similar to UISegmentedControl.
I started subclassing a UIView and i can create a custom UISegmentedControl with this code:
CustomSegment *segment = [[CustomSegment alloc]
initWithTitles:[NSArray arrayWithObjects:#"one",#"two",nil]];
[self.window addSubview:segment];
But now i'd like to improve my class and add some more customizable parameters to it.
For example i'd like add a custom separators, define the button fonts and so on... here my doubt:
Is it better to work on a UIView subClass or you suggest me to subclass a UIViewController, where i can manage View hierarchy in method like -(void)loadView and -(void)viewDidLoad ?
In a simple UIView subclass, when i launch the custom init method, i setup immediately subviews... while using a UIViewController i can call custom init and define how my subview is builded into -(void)loadView.
Don't use an UIViewController, just extend the UIView class like you did and keep extending its functionality.
Remember to save a pointer to each subview you add (i.e. buttons) in order to be able to access them later.
Define custom setters, for example, a custom setter for changing a button label title would be:
- (void) setButton1Title:(NSString*)str forState:(UIControlState)state{
//You can add some control here
if ([str length] > 20) return;
[_button1 setTitle:str forState:state]; //_button1 is my reference to the button
}
And so on. Don't provide direct access to your subviews, use methods instead.
Also, you can use "layoutSubviews" method to define how your views are going to be displayed in your custom view.
Hope it helps you.
Edit: In your case, I don't see why using lauoutSubviews method but I want to show you what I was trying to say.
Lets say that for example I need to create an UIView class to represent a "Contact" object in my application.
This is what I would do:
#interface ContactView : UIView{
UILabel* _nameLabel;
UILabel* _ageLabel;
Contact* _contact;
}
#property (retain) Contact* contact;
#end
#implementation ContactView
#synthetize contact = _contact;
-(id)initWithContact:(Contact*)c{
self = [super init];
if (self) {
_nameLabel = [[UILabel alloc] init];
_nameLabel.frame = CGRectZero;
[self addSubview:_nameLabel];
[_nameLabel release];
_ageLabel = [[UILabel alloc] init];
_ageLabel.frame = CGRectZero;
[self addSubview:_ageLabel];
[_ageLabel release];
self.contact = c;
}
}
- (void) layoutSubviews{
[super layoutSubviews];
_nameLabel.frame = CGRectMake(0.0f, 0.0f, 200.0f, 25.0f);
_ageLabel.frame = CGRectMake(0.0f, 25.0f, 200.0f, 25.0f);
if (self.contact){
_nameLabel.text = self.contact.name;
_ageLabel.text = self.contact.age;
}else{
_nameLabel.text = #"Unavailable";
_ageLabel.text = #"Unavailable";
}
}
- (void) setContact:(Contact*)c{
self.contact = c;
[self layoutSubviews];
}
#end
Check out how the "layoutSubiews" is used to set the correct frame and data to the labels.
Usually, I use it a lot when creating custom UITableViewCells where you have to reuse the view.
Let me know if I'm being confusing.