I have a UISegmentedControl whose "Value changed" event is wired up in Interface Builder to call my controller's -(IBAction)segmentChangeAction:(id)sender;
When the user taps on the control to change the selected segment, as expected segmentChangeAction is called whether in iOS4 or iOS5.
When I programmatically change the selected segment through segmentedControl.selectedSegmentIndex = newIndex;, on iOS4 segmentChangeAction is called and the segment reflects the new selection. However on iOS5 segmentChangeAction is not called, yet the segment does reflect the new selection.
Is this a change in iOS5? Is there anything I can do to get segmentChangeAction called on iOS5 when I programmatically change the selection?
This is a change in iOS 5 in order for UISegmentedControl to be consistent with all other controls.
The idea is that the action should only fired automatically as a result of user interaction. Prior to iOS 5, UISegmentedControl's actions would be fired because of user interaction and programmatic interaction. However, initiating the change programmatically means that you can also do [myControl sendActionsForControlEvents:UIControlEventValueChanged] yourself.
However, you have to be careful with this. Say you do:
[segmentedControl setSelectedSegmentIndex:newIndex];
[segmentedControl sendActionsForControlEvents:UIControlEventValueChanged];
If you build and run this on iOS 5, it works as you expect. If you build and run this on iOS 4, you'll get your actions fired twice (once when you setSelectedSegmentIndex and again when you sendActions...).
The way around this is to do some sort of guard. This could be a runtime check to indicate that you're running on an iOS 5+ device, or could even be something more mundane, like this:
// changingIndex is a BOOL ivar
changingIndex = YES;
[segmentedControl setSelectedSegmentIndex:newIndex];
changingIndex = NO;
[segmentedControl sendActionsForControlEvents:UIControlEventValueChanged];
and then in your action method...
- (void)segmentedControlSelectedIndexChanged:(id)sender {
if (!changingIndex) {
// your action code here, guaranteed to only run as a result of the sendActions... msg
}
}
I found another way, probably bit easier to understand
you can extend UISegmentedControl and add target action in init methods and call a delegate method to trigger the value change
here is the example code
header file looks like this
#import <UIKit/UIKit.h>
#class CGUISegmentedControl;
#protocol CGUISegmentedControlDelegate <NSObject>
#optional
- (void) segmentedControl:(CGUISegmentedControl *) control valueChangedTo:(NSInteger) nValue;
#end
#interface CGUISegmentedControl : UISegmentedControl
#property (nonatomic,unsafe_unretained) id <CGUISegmentedControlDelegate> delegate;
#end
.m file
#import "CGUISegmentedControl.h"
#implementation CGUISegmentedControl
#synthesize delegate = _delegateAction;
- (void) addTargetAction {
[self addTarget:self action:#selector(indexChanged:) forControlEvents:UIControlEventValueChanged];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self addTargetAction];
}
return self;
}
- (id) initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self addTargetAction];
}
return self;
}
- (id) initWithItems:(NSArray *)items {
self = [super initWithItems:items];
if (self) {
[self addTargetAction];
}
return self;
}
- (id) init {
self = [super init];
if (self) {
[self addTargetAction];
}
return self;
}
- (void) indexChanged:(id) sender {
if (_delegateAction && [_delegateAction respondsToSelector:#selector(segmentedControl:valueChangedTo:)])
[_delegateAction segmentedControl:self valueChangedTo:self.selectedSegmentIndex];
}
#end
And you can set the delegate in the calling class
Related
I have this cascade :
Somewhere in the app
- (void) go
{
MyCustomViewController* controller = [[MyCustomViewController alloc] init];
controller.delegate = self;
[controller run];
}
in MyCustomViewController
- (id) init
{
// there is an if statement here in real to choose another XIB if needed
// but I only display here the code that is called in my tests
self = [super initWithNibName:#"MyXIB" bundle:nil];
if (!self) return nil;
self.delegate = nil;
return self;
}
- (void) run
{
// do things with self.view
}
MyCustomViewController inherits GenericWindowController
in GenericWindowController
/*- (id) initWithNibName:(NSString*)nibNameOrNil bundle:(NSBundle*)nibBundleOrNil
{
if (!(self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) return nil;
self.view.userInteractionEnabled = NO; // THE APP CRASHES HERE ! self.view is nil
...
return self;
}*/
// METHOD created following first answers : NOT CALLED
- (void) viewDidLoad
{
self.view.userInteractionEnabled = NO;
// and many other things done with self.view
}
MyXIB has its File's owner set to MyCustomViewController and the view is connected.
All files are included and checked into the project.
GenericWindowController is designed to make some standard stuff.
MyCustomViewController extends this stuff to work with a custom View, as designed in MyXIB.
Why self.view is nil in GenericWindowController ?
Why viewDidLoad is not called ?
A view controller should not try to access its view in initWithNibName:bundle:. It should wait until viewDidLoad.
self.view is only valid after viewDidLoad -- in init... it is still nil.
Incredible !!! The problem was because of a missing localization for the XIB. Added the localization, and no problem anymore.
I have a view controlled app. On my xib I have no webviews but I do have buttons that bring up classes that have webviews. So hit button one and a uiwebview pops up so on and so forth.
Now in one of my classes I pull up a remote webpage that has a link on it. I want to over-ride that button link with shouldStartLoadwithrequest. How ever this never seems to get called.
Right now my class is called Tenth.m and I have this code in my view controller
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType {
NSLog(#" Test spot 1");
NSString* scheme = [[request URL] scheme];
if ([#"didTap" isEqual:scheme]) {
// Call your method
NSLog(#" Test spot 2");
[self didTapButton1];
return NO;
} else {
NSLog(#" Test spot 3");
return YES;
}}
But I get nothing (none of my NSLogs). I have tried putting this into my Tenth.m class and still nothing. I have looked at other examples and even downloaded one but it only uses the viewcontroller and delegates classes. No third class.
I'm lost as to why its not getting called.
Here is my third class
#import "Tenth.h"
#implementation Tenth
- (void)awakeFromNib
{
[category10 loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.mywebsite.com/api/didtap.php"]]];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
}
*/
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return YES;
}
#end
remote page code
hello
Tenth.h file
#import <UIKit/UIKit.h>
#interface Tenth : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *category10;
}
-(IBAction)back;
#end
My first guess is that the Web view does not have its delegate set. In order for it to call
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
The class containing this method needs to be set as the webview's delegate. So somewhere in the code there should be a line similar to webView.delegate = <variable>, where the variable is the instance of the class which has implemented the shouldStartLoadWithRequest: method.
This is often "self" when the webView is a subview of a viewController's view. But the way you have described your application, I am not sure if it will be self or not, but it sounds like it will not be self, but the name of the instance of a different class.
Maybe try this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Remember to set web view delegate to self.
category10.delegate = self;
}
I am building an application which should allow the user to scroll through the images. Since I have many images (downloaded from the web), what i doing is I have UP and down button on the parent view, in addition i have the scroll view. Based on the selected option (up or down), I add the ImageClass ( created a class which extends UIViewController) view to the scroll view.
Now, on the selected view the user can mark a point or do any stuff.
The question is how from the parent view I can call methods of the Uiviewcontroller. I know how I can set the delegate methods but what I want is that parent controller can call any a method say redraw method which would redraw the entire view.
Code:
-(IBAction) down:(id) sender{
[scrollView2 removeFromSuperview];
}
-(IBAction) down :(id) sender {
[scrollView2 removeFromSuperview];
if(scrollView2 == nil)
scrollView2 = [[UIScrollView alloc] init];
[self.view addSubview:scrollView2];
[self.view sendSubviewToBack:scrollView2];
[scrollView2 setBackgroundColor:[UIColor blackColor]];
[scrollView2 setCanCancelContentTouches:NO];
scrollView2.clipsToBounds = YES;
// default is NO, we want to restrict drawing within our scrollview
scrollView2.indicatorStyle = UIScrollViewIndicatorStyleWhite;
// CSImageView is a class which of the type UIViewController
imageVie = [[CSImageView alloc] init];
[scrollView2 addSubview:imageVie.view];
[scrollView2 setContentSize:CGSizeMake(1500,1500)];
[scrollView2 setScrollEnabled:YES];
[imageView release];
}
Now, from the parent view controller I want to call say:
imageVie.redraw(); method
Code for CSImageView
#interface CSImageView : UIViewController {
NSNumber *imageId ;
}
#property (nonatomic, retain) NSNumber *venueId;
-(void) redraw;
#end
#implementation CSImageView
#synthesize imageId;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
}
#pragma mark - View lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
-(void) redraw {
NSLog(#"I am ehre in the test function");
}
#end
Can you please help me on the same. I am not able to call the redraw method. Any input would be appreciated.
First, CSImageView should probably be a subclass of UIView, not UIViewController. You should keep a reference to your instance (i.e. as an instance variable or synthesized property) so you can access it from methods other than - down:.
Wow.
I can't say I follow what you've coded, but here's how you do it.
During this part...add a tag to the view
// CSImageView is a class type UIViewController
imageVie = [[CSImageView alloc] init];
imageVie.tag = 99 // Any number. Preferably a constant
[scrollView2 addSubview:imageVie.view];
...
Then on the parent
view controller. You can ...
-(IBAction) down:(id) sender {
CSImageView *view = [scrollView2 viewWithTag:99];
[view redraw];
... }
Something like that.
I went through the material regarding this issue but still can't figure it out completely.
The main issue is that i am using this method but the view does not show...
The app is built out of two view - the main one (i added it as a subview to the main nib) -
Has of a text field where i write some text.
Has a button that when pressed, the action method loads a second view that has a label with some text in it.
the source code of the first viewController:
#import <UIKit/UIKit.h>
#class TxtRunnerViewController;
#interface Itai_s_text_app_take_2ViewController : UIViewController {
int sliderSpeed;
IBOutlet UITextField *textInput;
TxtRunnerViewController *trvc;
}
#property (nonatomic, retain) IBOutlet UITextField *textInput;
#property (retain, nonatomic) TxtRunnerViewController *trvc;
- (IBAction)sliderChanged:(id)sender;//speed of text show changed
- (IBAction)textFieldDoneEditing:(id)sender;//dor 'DONE' on keyboard
- (IBAction)backgroundTap:(id)sender;//for handling tapping on background
- (IBAction)textEmButtonPressed:(id)sender;
#end
the .m of the first ViewController:
#import "Itai_s_text_app_take_2ViewController.h"
#import "TxtRunnerViewController.h"
#implementation Itai_s_text_app_take_2ViewController
#synthesize textInput;
#synthesize trvc;
- (IBAction)sliderChanged:(id)sender
{
UISlider *slider = (UISlider *)sender;
sliderSpeed = (int)(slider.value + 0.5f);//setting the speed determinned by the usr in slider
NSLog(#"Slider value is %#", sliderSpeed);
}
- (IBAction)textFieldDoneEditing:(id)sender
{
[sender resignFirstResponder];
NSLog(#"our text input is %#", textInput.text);
}
- (IBAction)backgroundTap:(id)sender
{
[textInput resignFirstResponder];
}
- (IBAction)textEmButtonPressed:(id)sender
{
NSLog(#"button pressed");
if ([textInput.text length]==0)
{
NSString *msg = nil;
msg = #"Write text to transmit";
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Forgot something?"
message:msg
delegate:self
cancelButtonTitle:#"Back"
otherButtonTitles:nil];
[alert show];
[alert release];
[msg release];
}
else { //init
NSLog(#"HI!");
if (self.trvc == nil)
{
NSLog(#"if accepted");
TxtRunnerViewController *tr = [[TxtRunnerViewController alloc] initWithNibName:#"TxtRunner"
bundle:nil];
self.trvc = tr;
[tr release];
}
[self.view removeFromSuperview];
[self.view insertSubview:trvc.view atIndex:0];
NSLog(#"InsertAtIndex was operated...!");
//[self.view addSubview:tvc.view];
}
}
/*
// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
*/
/*
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
}
*/
/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
[super viewDidLoad];
}
*/
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc that aren't in use.
if(self.trvc.view.superview == nil)
self.trvc = nil;
}
- (void)viewDidUnload {
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (void)dealloc {
[trvc release];
[super dealloc];
}
The second View is simply empty (has a label added to it in it's nib file):
#import
#interface TxtRunnerViewController : UIViewController {
}
#end
It's .m file is the default one, added no code to it.
When i press the button in the first view - it's view disappears but the second view does not appear instead, only a blank white view appears, nothing on it.
Doe
If you push the TxtRunnerViewController it will completely hide the first one, but it still exist in background. (without removeFromSuperview)
TxtRunnerViewController *vw = [[TxtRunnerViewController alloc]initWithNibName:#"TxtRunner" bundle:nil];
[self.view pushViewController:vw.view animated:NO];
[vw release];
To remove the first view it's a bit more difficult. You would need to implement a delegate in RootViewController which will be fired after clicking the done Button of the first view. Then in your RootViewController some code is executed which displays the second view and removes the first view.
For a delegate sample open the Utility Application Sample when creating a new project and figure out how it works or search some examples online.
The line:
[self.view removeFromSuperview];
Removes the view from its super
then the call
[self.view insertSubview:trvc.view atIndex:0];
Adds trvc to it - but that view has been removed and not visible anymore.
You need to add trvc to the superview.
I have a UISegmentedControl which I'd like to use to perform a certain action if you click the already selected item.
My thinking is basically something like this:
- (void)viewDidLoad {
UISegmentedControl * testButton = [[UISegmentedControl alloc] initWithItems:[NSArray arrayWithObjects:#"one", #"two", nil]];
[self.view addSubview:testButton];
[testButton addTarget:self action:#selector(clicked:) forControlEvents:UIControlEventTouchUpInside];
[super viewDidLoad];
}
-(void) clicked: (id) sender{
NSLog(#"click");
}
(And in clicked: I'd just do some check to see if the new selected index is different from the old selected index before the click)
The problem is I can't seem to override the action for the TouchUpInside control event. Any help appreciated!
-S
You can use a subclass to get the behavior you want. Make a subclass of UISegmentedControl that has one BOOL ivar:
BOOL _actionSent;
Then, in the implementation, override the following two methods:
- (void)sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
[super sendAction:action to:target forEvent:event];
_actionSent = TRUE;
}
- (void) setSelectedSegmentIndex:(NSInteger)toValue {
_actionSent = FALSE;
[super setSelectedSegmentIndex:toValue];
if (!_actionSent) {
[self sendActionsForControlEvents:UIControlEventValueChanged];
_actionSent = TRUE;
}
}
There are probably other ways but this worked ok for me. I'd be interested to learn of other approaches.
Use UIControlEventValueChanged with UISegmentedControls.