My table view has 2 sections where in the first section has a switch and other section has couple of table view cells. When the switch if on, the cells should be visible and selected and when the switch is off the cells should be hidden.
If my cellForRowAtIndexPath for my first section I execute a selector when the user changes the switch state as per below:
- (UITableViewCell *)cellForRowAtIndexPathForFirstSection
{
[self.tableViewFirstCell.mySwitch addTarget:self action:#selector(changeState:) forControlEvents:UIControlEventValueChanged];
return self.tableViewFirstCell;
}
changeStateMethod:
- (void)changeState:(UISwitch *)sender
{
if ([sender isOn])
{
//Show other section and select its cells.
}
// Reload the table view.
[self.tableView reloadData];
if (![sender isOn])
{
// Collect the selected cells data and hide the section.
}
}
There are scenarios when the user lands on the view for the first time, the system should handle changing the state of the switch. If the switch should be made ON I have:
[self.tableViewFirstCell.mySwitch setOn:YES animated:YES]; or to make it OFF I have:
[self.tableViewFirstCell.mySwitch setOn:NO animated:YES];
QUESTION:
So whether the switch is changed by the system or by the user, the logic to be executed after changing the state of the switch is same in both the cases. In short, when the user changes the switch the selector method gets called, but when the system changes the state is there a way I can call the selector which figures out what state is the switch changed to and then execute the logic appropriately as it does when the user changes the switch?
For example: When the user changes the switch state to ON, logic inside "if ([sender isOn])" gets executed. I want to execute this logic when the system changes the state of the switch to ON.
The delegate methods are only called when a user interacts with the control. If you want the same event processed when you change the state via code, simple call the event handler yourself.
[self.tableViewFirstCell.mySwitch setOn:NO animated:YES];
[self changeState:self.tableViewFirstCell.mySwitch];
That's it. Nice and simple.
Related
I have a table view with user posts that can be upvoted and downvoted. I have two custom buttons for the upvote and downvote in the cells, which I use like so:
// in cellForRowAtIndexPath:
[cell.upVote addTarget:self action:#selector(handleThumbsUp:) forControlEvents:UIControlEventTouchUpInside];
[cell.downVote addTarget:self action:#selector(handleThumbsDown:) forControlEvents:UIControlEventTouchUpInside];
//the methods
- (IBAction)handleThumbsUp:(ThumbsUpButton *)sender {
if (sender.selected == YES) {
[sender setSelected:NO];
} else {
[sender setSelected:YES];
}
}
- (IBAction)handleThumbsDown:(ThumbsDownButton *)sender {
if (sender.selected == YES) {
[sender setSelected:NO];
} else {
[sender setSelected:YES];
}
}
When the "Thumbs Up" button is selected, and the user changes his mind and presses "Thumbs Down", how can I deselect the "Thumbs Up" button in that same cell?
You should have a model that contains the "thumbs up/down" information; you should not be storing it in your views in the form of the button being selected or not.
When one of the buttons is tapped, your controller should update the model and refresh the view based on the state of the model.
(Some kind of binding system would make this easier: ReactiveCocoa is one such option (though it's much more than just model/view bindings); another, much simpler (shameless link to my own free code) is my own UIViewController+WSSDataBindings category.)
By #selector you can access a property of the button not another control of the cell in a button handler method.
So, You must have update whole cell on the button handler method and manually handle the selected state of buttons in cellForRowAtIndexpath delegate method of table.
For simplest solution (with minimum structure change and code) it may be achieved with moving button action methods to your custom cell class. then add actions to upvote and downvote in cellForRowAtIndexPath:
// in cellForRowAtIndexPath:
[cell.upVote addTarget:cell action:#selector(handleThumbsUp:) forControlEvents:UIControlEventTouchUpInside];
[cell.downVote addTarget:cell action:#selector(handleThumbsDown:) forControlEvents:UIControlEventTouchUpInside];
Or you can directly set this methods from Nib file.
and then change the upvote / downvote methods like this.
//the methods
- (IBAction)handleThumbsUp:(ThumbsUpButton *)sender {
if (sender.selected == YES) {//upvote undone
[sender setSelected:NO];
} else {//upvote done
[self.upVote setSelected:YES];
[self.downVote setSelected:NO];//delesect downvote
}
}
- (IBAction)handleThumbsDown:(ThumbsDownButton *)sender {
if (sender.selected == YES) {//downVote undone
[sender setSelected:NO];
} else {
[self.downVote setSelected:YES];
[self.upVote setSelected:NO];//deselect upvote
}
}
also as Josh Caswell stated in his answer, you should have a data for the user's upvote and downvote in your dataModel you fill your cell. This is just a quick answer for this specific case, but to support dataModel changes you can add reference to your model inside your cell and modify it inside this action methods for saving votes.
iOS6
I have 6 UITextFields in a Scene with a Next Button that Segue's into the next Scene.
The code below works great when I hit the Done button on the keyboard:
- (IBAction)dismissKeyboard:(id)sender
{
if (sender == LocationName) {
self.meeting.LocationName = LocationName.text;
}
if (sender == LocationAddress) {
self.meeting.LocationAddress = LocationAddress.text;
}
if (sender == LocationCity) {
self.meeting.LocationCity = LocationCity.text;
}
//I have 3 more text fields after and then I persist to CoreData:
NSError *error = nil;
if (![managedObjectContext save:&error]) {
}
[sender endEditing:YES];
}
If the user hits the Done Button on the Keyboard, the data saves fine.
However, if the User hits the Next Button on the Navigation Bar, without hitting the Done button on the keyboard first, the user Typed inside the UITextfield DOES NOT SAVE. I would like for the user typed Data (Data that user inputted from the keyboard) in all the fields to be saved when the user hits the Next Button on the Navigation Bar to call the next scene.
I have the following code for the Next Button:
- (IBAction)nextButtonPressed:(id)sender {
[self dismissKeyboard:sender];
}
I know the code for nextButtonPressed is wrong. I think I need help with identifying which UITextField called the Keyboard to be visible, and then call the dismissKeyboard with passing the Sender as a parameter.
Thanks
Make use of the UITextField delegate method textFieldDidEndEditing: to know when the focus leaves a text field. This is when you should save its data. This will be called when focus moves to another text field or when the keyboard is completely dismissed.
Your implementation of nextButtonPressed: should simply call becomeFirstResponder on whatever the next text field is. That's it. By making another text field the first responder, the previous one will have its textFieldDidEndEditing: delegate called.
Update:
// This assumes the LocationXXX are instance variables referencing the UITextFields
- (IBAction)nextButtonPressed:(id)sender {
if (sender == LocationName) {
[LocationAddress becomeFirstResponder];
} else if (sender == LocationAddress) {
[LocationCity becomeFirstResponder];
// add the rest as needed
}
}
How can i gather sent events from a UITextField in one place or am i forced to create outlets and actions for every single event i intend to use?
If I got your question correctly you need to get the text from UITextField when this UITextField loose focus (user taps elsewhere). To achieve this you need:
Declare your class as (in yourClassName.h
file)
Implement in yourClassName.m file this method:
- (void)textFieldDidEndEditing:(UITextField *)textField {
NSString *someStringOrWhateverYouNeed = textField.text;
}
Any time user will press return button on keyboard your class will have a notification and call this method.
In case if you need to gather event from multiple UITextFields you can mark all your textField with specific tags and create one IBAction like this:
- (IBAction)getTextFieldEvent:(id)sender {
UITextField *currentTextField = (UITextField *)sender;
switch (currentTextField.tag) {
case 1:
// some code here for textField with tag = 1
break;
case 2:
// some code here for textField with tag = 2
break;
case 3:
// some code here for textField with tag = 3
break;
default:
// some default code here
break;
}
}
For different event types I can suggest to create different IBAction's. If you do not need to change UITextField's properties (e.g. font etc.) then you do not really need IBOutlets.
Hope that helps :)
Do another IBAction connection with the same name. Then erase the duplicate method. Both textfields will be connected to the same IBAction method.
I've looked at the Apple appPrefs code sample, but that seems to be for navigation controllers only. I'm working with an iPad UISplitViewController that has simple root and detail VCs.
I can change certain settings (colors, date formats, etc) but currently, I have to restart the app to have the changes effected. I would prefer not to have to restart the app.
I'm using a system of loading the settings when the app starts each time. I can get a notification system to work, but I don't know how to reload the view controllers.
Any ideas how to do this (I guess reload the views somehow).
Thanks for any tips/advice. I can post some code if relevant.
If you use settings bundle to manage preferences from the Settings app:
From what you said in your question, you already know how to get a notification(UIApplicationDidBecomeActiveNotification) when your app becomes active, right?
If so, the only problem left is how to reload your view after you receive the notification. Other than UITableView, which can be easily reloaded by calling [tableView reloadData], you have to reload your view by assigning values to the UI controls that you want to reload just as you set them up initially. Say you have a UILabel label you want to reload with the newly set preference value, you just write code like this:
- (void)reloadView {
label.text = [[NSUserDefaults standardUserDefaults] stringForKey:#"PreferenceKey"];
self.view.background = …
[self.tableView reloadData];
}
- (void)reloadViewOnAppActivation:(NSNotification *)notif {
[self reloadView];
}
If you are using in app preferences setting:
If the preferences view controller does not display simultaneously with the SplitViewController. Reload your views in their controllers' viewWillAppear: methods:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self reloadView]; // See the definition of reloadView above
}
Otherwise, make the SplitViewController the delegate of, or assign it to an ivar of, the preferences view controller, and notify it of the preferences changes when appropriate — immediately after changing any single preference if you prefer in realtime update, or after all the changes are done if you prefer batch update:
// SplitViewController methods:
- (void)preferencesAreChanged {
[self reloadView]; // See the definition of reloadView above
}
// Preferences view controller methods:
// Immediate update, use a preference controlled by a `UISegmentedControl` as an example
- (void)viewDidLoad {
[super viewDidLoad];
…
[segmentedControl addTarget:self action:#selector(xPreferenceTogglingAction:) forControlEvents:UIControlEventValueChanged];
…
}
- (IBAction)xPreferenceTogglingAction:(id)sender {
// Update the x preference.
…
[delegate preferencesAreChanged];
}
// Batch update
- (void)viewWillDisappear:(BOOL)animated {
[delegate preferencesAreChanged];
[super viewWillDisappear:animated];
}
So, to help others, I will post how I (with help from Apple) solved this.
In both root and detail view controllers, I added in styles based on user settings:
"Warm Tones", "Cool Tones", "Leather" etc. These translate to code like this:
switch (styleKey) {
case 0: // BASIC
fontName = #"Copperplate";
fontSize = 16;
selectedBarColor = [UIColor lightGrayColor];
selectedTintColor = [UIColor lightGrayColor];
selectedFontColor = [UIColor darkGrayColor];
backgroundColor = [UIColor whiteColor];
selectedHighlightColor = UITableViewCellSelectionStyleGray;
backgroundImage = nil;
detailBackgroundImage = nil;
break;
Then, whenever a color/style/font is called, I used something like this:
cell.selectionStyle = selectedHighlightColor;
cell.backgroundColor = backgroundColor;
This allowed me to change the settings and styles, but I still had to restart the app each time to see the changes.
The fix turned out to be simple.
Settings the styles changed the values of the constants (e.g. fontColor) - but I wasn't actually changing the fields.
So at the end of the switch statements, all I added was something like this:
self.tableView.backgroundColor = backgroundColor;
self.navigationController.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:backgroundImage]];
self.navigationController.navigationBar.tintColor = selectedBarColor;
self.tableView.separatorColor = selectedTintColor;
I had to do this in both view controllers.
Also, all this code was part of a routine (changeSettings).
This method is being observed to look for changes.
The way I handled the in-app preference look and feel (a modal VC) was to use the terrific InAppSettingsKit.
I hope this helps others. Most of you will find this a no-brainer I expect, but - having not much brain left - it took me two weeks to figure it out.
I configure my search bar to show the results button, but the button only shows until the user enters a character. At that point, the "X" cancel button replaces it. So without entering characters, the search result set equals the entire data set. I'd like the results button to stay there so when the user has typed enough characters to get a smaller result set (like 5 or 6 rows), they can click the results button, my delegate will get called, and I can show just that result set.
UISearchBar * theSearchBar = [[UISearchBar alloc]
initWithFrame:CGRectMake(0,0,700,40)];
theSearchBar.delegate = self;
theSearchBar.placeholder = #"What are you looking for?";
theSearchBar.showsCancelButton = NO; // shows up after first char typed.
theSearchBar.showsSearchResultsButton = YES; // disappears just when I need it.
...further down in the VC... this method can only called when the search bar's input field is empty.
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar {
NSLog(#" searchBarResultsListButtonClicked for %#",searchBar); //
}
Advice, tutorials, sample code and justified dope-slaps welcome.
TIA
-Mike
#Rayfleck, I think you should not worry about Search Results Button at all.
If what you need is to monitor user's input until they have entered enough characters for filtering:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText {
if ([searchText length]>5) {
[self filterDataWithKeyword:searchText];
[self.tableView reloadData];
} else {
[self resetFilter];
[self.tableView reloadData];
}
}
Here is a partial answer that you can stick in viewDidLoad. It should hide the clear button, but it doesn't keep the results button visible. I'm not sure how the results button view logic is controlled behind the scenes.
for (id subview in mySearchBar.subviews) {
if ([[subview class] isSubclassOfClass:[UITextField class]]) {
[subview setClearButtonMode:UITextFieldViewModeNever];
break;
}
}
Since this approach uses all public APIs your app shouldn't get rejected. Although this approach might be prone to breaking further down the road if/when Apple decides to change the hierarchy of UISearchBar. All I'm doing is looking for the UITextField or subclass and setting its clearButtonMode.
Hope this helps.