I am working on a quiz application, where I get the quiz items from an api. Right now, each question consist of an image and 4 possible answers. Currently, I have two views attached to my view controller(front view and back view), and they change to one and other, when an answer button is clicked(I am only displaying the first two questions this way).
Right now with each api call, I get 7 items(meaning 1 image and 4 answers for each item). But amount of questions can change anytime, so I am trying to implement a way to automate the view creation process.
Based on what I searched this is what I was able to come up with it:
QuizMainViewController.h
#import <UIKit/UIKit.h>
#interface QuizMainViewController: UIViewController <UIScrollViewDelegate>
#property (nonatomic,retain)NSArray *results;
#property (nonatomic,assign)NSUInteger currentItemIndex;
#property(nonatomic,assign)NSUInteger numberOfItemsIntheResultsArray;
QuizMainViewController.m
#import "QuizMainViewController.m"
#import "AppDelegate.h"
#import "Base64.h"
#import "Item.h"
#import "SVProgressHUD.h"
#import <UIKit/UIKit.h>
#interface QuizMainViewController ()
#property(nonatomic,strong)IBOutlet UIView *frontView;
#property (nonatomic,strong)IBOutlet UIView *BackView;
//first view outlets
#property (strong, nonatomic) IBOutlet UIImageView *frontViewImage;
#property (nonatomic,weak) IBOutlet UIButton *frontViewAnswer1;
#property (nonatomic,weak) IBOutlet UIButton *frontViewAnswer2;
#property (nonatomic,weak)IBOutlet UIButton *frontViewAnswer3;
#property (nonatomic,weak) IBOutlet UIButton *frontViewAnswer4;
//second view outlets
#property (strong, nonatomic) IBOutlet UIImageView *backViewImage;
#property(nonatomic,weak) IBOutlet UIButton *backViewAnswer1;
#property(nonatomic,weak) IBOutlet UIButton *backViewAnswer2;
#property(nonatomic,weak) IBOutlet UIButton *backViewAnswer3;
#property(nonatomic,weak) IBOutlet UIButton *backViewAnswer4;
#implementation QuizMainViewController
//the method that I am making the api call
-(void)loadQuizObjectsMethod
{
// this is the part I make the call and try to set the views based on if the items.count is odd or even.
operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *req, NSHTTPURLResponse *response, id jsonObject)
{
NSArray *array = [jsonObject objectForKey:#"Items"];
NSLog(#"Item array: %#",array);
NSMutableArray *tempArray = [[NSMutableArray alloc] init];
for (NSDictionary * itemDict in array) {
Item *myResponseItems = [[Item alloc] initWithDictionary:itemDict];
[tempArray addObject:myResponseItems];
}
_results = tempArray;
//this is where I need help with
Item *item = [_results objectAtIndex:_numberOfItemsIntheResultsArray];
for (_numberOfItemsIntheResultsArray in _results) {
if (_currentItemIndex %2 == 0) {
_currentItemIndex
//implement the front view
}else if (_currentItemIndex %2 == 1)
{
//implement the back view
}
}
I appreciate your help.
You need to use UIPageViewController class in such situations. First create a class called QuestionViewController as follows
QuestionViewController.h
#property (strong, nonatomic) IBOutlet UIImageView *image;
#property (nonatomic,weak) IBOutlet UIButton *answer1;
#property (nonatomic,weak) IBOutlet UIButton *answer2;
#property (nonatomic,weak)IBOutlet UIButton *answer3;
#property (nonatomic,weak) IBOutlet UIButton *answer4;
#property (strong, nonatomic) Item *questionDetailsFromJSON;
QuestionViewController.m
-(void)viewDidLoad
{
self.image = self.questionDetailsFromJSON.image
//set all remaining outlets similar to this.
}
In the storyboard or xib embed a UIPageViewController in your QuizMainViewController using aUIContainer. Set QuizMainViewController as the delegate to the page view controller.
Change your QuizMainViewController.m as follows
#interface QuizMainViewController ()
#property(nonatomic,weak)IBOutlet UIPageViewController *pvc;
#implementation QuizMainViewController
//Implement all of the other methods
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
int index = [self.results indexOfObject:[(QuestionViewController *)viewController question]];
index++;
QuestionViewController *qvc = nil;
if(index<self.results.count)
{
qvc = //Instantiate from story board or xib
qvc.questionDetailsFromJSON = [self.results objectAtIndex:index];
}
return qvc;
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
int index = [self.results indexOfObject:[(QuestionViewController *)viewController question]];
index--;
QuestionViewController *qvc = nil;
if(index>=0)
{
qvc = //Instantiate from story board or xib
qvc.questionDetailsFromJSON = [self.results objectAtIndex:index];
}
return qvc;
}
Follow this tutorial for further details.
The other poster's suggestion about using a page view controller is good. That would let you manage the pages with questions on them in a nice clean way.
As to your question of a variable number of fields on the form:
The simplest thing to do is to build your form for the maximum number of questions. Then, when you display it, hide those buttons/images that are not used for the current question.
You can create outlets for each button/image and then manipulate them directly, or you can assign tag numbers to them. If you use tag numbers it's a little easier to write code that shows the correct number of answer buttons.
Alternatively you could write code that creates and adds the buttons to the view controller hierarchy on the fly, but creating view objects directly in code is a little more work than doing it with IB. That's more than I can explain in detail in a forum post. For that, you should probably search for a tutorial on creating buttons through code rather than IB. It end up being like 4 or 5 lines of code per button.
A rough outline of the steps: You have to create a button using initWithFrame or the UIButton buttonWithType class method. You have to add a target/action to the button. You have to set it's title. You have to set it's frame, you have to save a pointer to it in an instance variable or an array, and you have to add it as a subview to your view controller's content view.
Related
I have an app where I need to present an overlay for feedback. I started by simply creating the exact thing I wanted within the UIViewController I wanted. However this presents to 2 problems. 1) I can't reuse this in another view (As I need to now) and 2) Because it's an overlay, it covers the entire UIViewController on the storyboard so I can't see the controls beneath it.
I looked at moving to an external UIView .xib file and loading dynamically which worked great, except whatever I did, I couldn't never get a handle on the labels within the nib to update the text.
Then I decided that making it a class and creating a delegate method for it would probably be the best way forward.
I have created a very simply .xib and laid it out as well as a .h and .m file (overlayView) and wired it all in an it looks good, except when trying to present the overlayView I get a exc_bad_access on the line
[window addSubview:self];
And I can't work out why. Full code below:
overlayView.h
#import <UIKit/UIKit.h>
#class overlayView;
#protocol overlayDelegate;
#interface overlayView : UIView
#property (nonatomic, strong) id <overlayDelegate> delagate;
-(instancetype)initWithTitle:(NSString *)title
dateFrom:(NSString *)dateFrom
dateTo:(NSString *)dateTo
description:(NSString *)description;
#property (strong, nonatomic) IBOutlet UILabel *overlayTitleLbl;
#property (strong, nonatomic) IBOutlet UILabel *overlayDateFromLbl;
#property (strong, nonatomic) IBOutlet UILabel *overlayDateToLbl;
#property (strong, nonatomic) IBOutlet UILabel *overlayDescLbl;
#property (strong, nonatomic) IBOutlet UILabel *overlayIcon;
-(void)showOverlay;
-(void)dismissOverlay;
#end
#protocol overlayDelegate <NSObject>
#optional
#end
overlayView.m
#import "overlayView.h"
#import "NSString+FontAwesome.h"
#implementation overlayView
- (instancetype)initWithTitle:(NSString *)title dateFrom:(NSString *)dateFrom dateTo:(NSString *)dateTo description:(NSString *)description {
self.overlayViewTitleLbl.text = title;
self.overlayViewDateFromLbl.text = dateFrom;
self.overlayViewDateToLbl.text = dateTo;
self.overlayViewDescLbl.text = description;
self.overlayViewIcon.text = [NSString fontAwesomeIconStringForIconIdentifier:#"fa-calendar"];
return self;
}
-(void)showOverlay {
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
[window addSubview:self]; <-- Code causing issue
[window makeKeyAndVisible];
}
-(void)dismissOverlay {
// Not wired in yet
}
#end
The being called in my main view controller like:
overlay = [[overlayView alloc] initWithTitle:[tmpDict objectForKeyedSubscript:#"Title"] dateFrom:startDate dateTo:stopDate description:[tmpDict objectForKeyedSubscript:#"Desc"]];
[overlay showOverlay];
Any ideas why this doesn't want to play ball? I have breakpointed the initWithTitle method and all information is being passed correctly so I think I am very close to what I am trying to achieve.
you need to initiate your view first, you're returning self without initiating it
- (instancetype)initWithTitle:(NSString *)title dateFrom:(NSString *)dateFrom dateTo:(NSString *)dateTo description:(NSString *)description {
self = [super init];
if (self) {
self.overlayViewTitleLbl.text = title;
self.overlayViewDateFromLbl.text = dateFrom;
self.overlayViewDateToLbl.text = dateTo;
self.overlayViewDescLbl.text = description;
self.overlayViewIcon.text = [NSString fontAwesomeIconStringForIconIdentifier:#"fa-calendar"];
}
return self;
}
I am new to the autolayout thing, was trying to design the given screen for Compact Width|Regular Height via Storyboard. It seems Very simple cause there is none dynamic fields. I was following apple documentation, and setting constraints for each UIView. Its working fine if there would be no View(shown in purple color). But there is some requirement(have to hide the View based on star rating), thats why added this UIView containing dispatch details, complaint category, screenshot etc. e.g- For Invoice Number I set Top Space,Leading Space, Trailing Space and for the label(shown in green color) Top Space, Leading Space, Trailing Space so that height of invoice Number wouldn't be ambiguous rep, Repeated same for all Views. UIView(purple) constraints are Trailing Space to:SuperView, Bottom space to:SuperView, Bottom space to:Remarks Equals:15, Top Space Equals to:StarRating Equals:8. After that I have added constraints for the fields inside the purple View same as the other Views like Invoice Number. But not getting the desired result for different screen size. Any help would be much appreciated.
Use a UITableView to lay out this screen. You can use a static content table view, laid out entirely in the storyboard. Make the purple part its own section. In your UITableViewController subclass, you don't need to implement most data source / delegate methods (because you're using static content). Just override tableView:numberOfRowsInSection: to return 0 for the optional section if you don't want to show it, and use -[UITableView reloadSections:withRowAnimation;] to update the table view. Here's my test code:
#import "ViewController.h"
#interface ViewController ()
#property (strong, nonatomic) IBOutlet UITableViewCell *topMarginCell;
#property (strong, nonatomic) IBOutlet UIButton *star1;
#property (strong, nonatomic) IBOutlet UIButton *star2;
#property (strong, nonatomic) IBOutlet UIButton *star3;
#property (strong, nonatomic) IBOutlet UIButton *star4;
#property (strong, nonatomic) IBOutlet UIButton *star5;
#property (strong, nonatomic) NSArray<UIButton *> *starButtons;
#property (nonatomic) NSInteger rating;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.starButtons = #[ self.star1, self.star2, self.star3, self.star4, self.star5 ];
self.tableView.backgroundColor = self.topMarginCell.backgroundColor;
}
- (void)reloadDispatchSection {
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationNone];
}
- (IBAction)starButtonWasTapped:(UIButton *)button {
NSInteger buttonIndex = [self.starButtons indexOfObject:button];
if (buttonIndex == NSNotFound) { return; }
self.rating = buttonIndex + 1;
[self.starButtons enumerateObjectsUsingBlock:^(UIButton * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj setTitle:(idx <= buttonIndex ? #"★" : #"☆") forState:UIControlStateNormal];
}];
[self reloadDispatchSection];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 1 && (self.rating == 0 || self.rating >= 4)) {
return 0;
} else {
return [super tableView:tableView numberOfRowsInSection:section];
}
}
#end
Result:
To summarize my app, it's like an iBook : I have multiple pages filled in with an xml file (text and image).
Like in the iBook app, I want to use a UISlider to select and change page.
I use a UIViewController (appViewController) as contener of the different UIViews (contentViewController)
header of AppViewController :
#import <UIKit/UIKit.h>
#import "contentViewController.h"
#interface AppViewController : UIViewController <UIPageViewControllerDataSource>
{
UIPageViewController *pageController;
NSArray *pageContent;
NSDictionary *pageFields;
}
in AppViewController.m, I have :
// Create a new view controller and pass suitable data.
contentViewController *dataViewController = [[contentViewController alloc]initWithNibName:#"contentViewController" bundle:nil];
The header of the contentViewController is :
#import <UIKit/UIKit.h>
#class DCIEquipment;
#interface contentViewController : UIViewController
#property (nonatomic,strong) DCIEquipment *eqt;
#property (assign, nonatomic) NSUInteger pageIndex;
#property (assign, nonatomic) NSUInteger pageNumber;
#property (strong, nonatomic) IBOutlet UISlider *pageSlider;
#end
Because in iBook, the slider seems to follow the curling movement of the page, I put my UISlider on the contentViewController. Everything works good with the UISlider but I don't how know to do to informe the UIViewController to change the page (I assumed that the code of changing the page has to be done in this controller).
Please tell me if you need some more code. I hope that explainations are clear. I juste want to know where to put the UISlider (which view) and how to send value to the pageViewController.
Thank you in advance.
Raphael
Use the protocols. There are lots of examples. And you are using at least one of them (UIPageViewControllerDataSource).
Just create one for your contentViewController (in it's header file)
#class ContentViewController; // this is fast-forward declaration
#protocol ContentViewControllerDelegate
- (void)contentViewController:(ContentViewController *)controller didChangePage:(NSUInteger)pageIndex;
#end
#interface ContentViewController : UIViewController
// ...
#property (weak,nonatomic) id<ContentViewControllerDelegate> delegate; // don't forget to set the value with your appViewController
// ...
#end
In your ContentViewController-implementation file in some method just call the delegate method
if (self.delegate && [self.delegate respondsToSelector:#selector(contentViewController:didChangePage:)])
[self.delegate contentViewController:self didChangePage:newIndex];
And don't forget to implement this method in appViewController class.
I created an Xcode app which stores user input using the core data framework in this case reviews (detail and rating 0-5).
So basically I have managed to create a view controller which stores the user inputs and then displays it in a table view controller.
Users are able to then choose any existing reviews they want from the table view controller, which then takes them to a new view controller which displays that specific review the user chose.
The problem is that I have managed to display the string value using this code "[self.detailView setText:[self.managedObject valueForKey:#"detail"]];" but I cannot get it to display the rating value which is stored as integer(16) in the core data.
.h detail view controller
#import <UIKit/UIKit.h>
#import "reviewTableViewController.h"
#import "reviewViewController.h"
#import "Reviews.h"
#interface reviewDetailViewController : UIViewController <NSFetchedResultsControllerDelegate, UITextFieldDelegate> //adding fetched controller delegate and text field delegate
#property (strong, nonatomic) NSManagedObjectContext *managedObjectContext;
#property (strong, nonatomic) IBOutlet UITextView *detailView; //holds detail values
#property (nonatomic, strong) Reviews *reviews; //property for holding reviews
#property (strong) NSManagedObject *managedObject; //hold core data property
#property (strong, nonatomic) IBOutlet UILabel *ratingView; //holds rating values
#property (strong, nonatomic) IBOutlet UIButton *myButton;
#property (strong, nonatomic) IBOutlet UIButton *myButton1; //buttons for borders
#end
.m detail view controller
- (void)viewDidLoad
{
[super viewDidLoad];
_myButton.layer.borderWidth = .5f;
_myButton.layer.borderColor = [[UIColor lightGrayColor]CGColor]; //adding borders for tet displa using buttons, when screen loads
_myButton1.layer.borderWidth = .5f;
_myButton1.layer.borderColor = [[UIColor lightGrayColor]CGColor];
[self.detailView setText:[self.managedObject valueForKey:#"detail"]]; //displaying detail value
//[self.ratingView setText:[self.managedObject valueForKey:#"rating"]];
//[self.ratingView setValue:[NSNumber numberWithInteger:managedObject] forKey:#"rating"];//displaying rating value
//[self.puzzle setValue:[NSNumber numberWithInt:timeTaken] forKey:#"bestTime"];
}
.m table view controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Get a reference to our detail view
// Pass the managed object context to the destination view controller
// [super prepareForSegue:segue sender:sender];
// If we are editing a picture we need to pass some stuff, so check the segue title first
if ([[segue identifier] isEqualToString:#"detailSegue"]) //using segue link name given
{
//Get the row we selected to view
NSManagedObject *managedObjects = [self.reviewsArray objectAtIndex:[[self.tableView indexPathForSelectedRow] row]]; //choosing the selected cell value
reviewDetailViewController *destViewController = segue.destinationViewController;
destViewController.managedObject = managedObjects; //displaying it in the destination controller
}
}
You need to convert the integer into a string.
[self.ratingView setText:[NSString stringWithFormat:#"%#", [self.managedObject valueForKey:#"rating"]]];
NSNumber *x = managedObject.rating; // rating is NSNumber!
int y = x.intValue; // this is how you get an int!
label.text = [NSString stringWithFormat:#"%i", managedObject.rating.intValue];
I am creating an iPad application (iOS6.1) which has a master detail view concept. First view is a table view has list of items that are been loaded from Plist, when each row gets selected the second table view gets loaded with another Plist. theirs is my Detail view which has to display an UIView with a UILabel ans an UIImage. I am using didSelectRowAtIndexPath method . The first two table Views are been displayed properly and loads the row and display corresponding View but the last detail view which is supposed to display the UILabel and an image is empty, can any one help me to solve this problem
My Code for the didSelectRowAtIndexPath method is
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
TaskDetailViewController *newTaskDetailViewController = [[TaskDetailViewController alloc] init];
// pass the row to newDetailViewController
if (weekNumber == 0)
{
newTaskDetailViewController.taskdescription = [weeklist1 objectAtIndex:indexPath.row];
}
if (weekNumber == 1)
{
newTaskDetailViewController.taskdescription = [weeklist2 objectAtIndex:indexPath.row];
}
if (weekNumber == 2)
{
newTaskDetailViewController.taskdescription = [weeklist3 objectAtIndex:indexPath.row];
}
// ...... repeated for 39 times because of the list
newTaskDetailViewController.taskNumber = indexPath.row;
[self.navigationController pushViewController:newTaskDetailViewController animated:YES];
}
DetailView header
#import <UIKit/UIKit.h>
#interface TaskDetailViewController : UIViewController
#property int taskNumber;
#property(strong , nonatomic) NSString *taskdescription;
#property (nonatomic , strong) NSMutableDictionary * tasks;
#property (strong, nonatomic) IBOutlet UIImageView *questionImage;
#property (strong, nonatomic) IBOutlet UILabel *displayText;
#end
Implemetation file has
#implementation TaskDetailViewController
#synthesize taskNumber;
#synthesize taskdescription;
#synthesize tasks;
#synthesize displayText;
#synthesize questionImage;
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.title = taskdescription;
NSLog(#"%#", taskdescription);
}
Your problem is using alloc init to create an instance of TaskDetailViewController. You've created that controller in the storyboard so you should instantiate it from the storyboard using an identifier that you give it (DetailViewController in my example):
TaskDetailViewController *newTaskDetailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];