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:
Related
I'm facing a bit complicated (at least it looks like it to me) problem with a custom UIView that I made (called EventBadge).
Here's the code of my custom class:
EventBadge.h
#interface EventBadge : UIView
- (void)setBadgeFillColor:(UIColor *) color;
- (void)setBadgeBorderColor:(UIColor *) color;
- (void)setBadgeIcon:(MyCustomIcons) icon;
#end
EventBadge.m
#implementation EventBadge
UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;
- (void)drawRect:(CGRect)rect {
// Gets graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
// Sets fill and border colors for cirlce
CGContextSetFillColor(context, CGColorGetComponents([badgeFillColor CGColor]));
CGContextSetStrokeColor(context, CGColorGetComponents([badgeBorderColor CGColor]));
// Set border line width
CGContextSetLineWidth(context, 2.0);
// Set rect containing circle as inset of rect
CGRect circle = CGRectInset(rect, 1, 1);
// Draw fill and stroke into rect
CGContextFillEllipseInRect(context, circle);
CGContextStrokeEllipseInRect(context, circle);
// Draws icon
[self drawBadgeIconInside:circle];
// Fill graphic context with path
CGContextFillPath(context);
}
/**
* Sets the background color for the badge and forces refresh
*/
- (void)setBadgeFillColor:(UIColor *) color{
badgeFillColor = color;
[self setNeedsDisplay];
}
/**
* Sets the background color for the badge and forces refresh
*/
- (void)setBadgeBorderColor:(UIColor *) color{
badgeBorderColor = color;
[self setNeedsDisplay];
}
/**
* Sets the icon for the badge and forces refresh
*/
- (void)setBadgeIcon:(MyCustomIcons) icon{
badgeIcon = icon;
[self setNeedsDisplay];
}
/**
* Draws the badge icon inside a rectangle
*/
- (void)drawBadgeIconInside:(CGRect) rect {
// Creates the inner rectangle from the original one (20x20)
CGRect iconContainer = CGRectInset(rect, 5, 5);
// Switch on badgeIcon: many different supported types
switch (badgeIcon) {
case EventLocation:
[StyleKit drawIconLocationWithFrame:iconContainer colorBase:[StyleKit blackMP]];
break;
case EventCar:
[StyleKit drawIconCarWithFrame:iconContainer colorBase:[StyleKit blackMP]];
break;
default:
MyLog(MyLogLevelError, #"INVALID MyCustomIcon");
break;
}
}
#end
I have a UITableView that can be filled with three different types of UITableViewCell, let's say TypeA, TypeB and TypeC.
TypeA and TypeB have different elements inside (UILabels, UIViews and so on) and they both have my EventBadge. TypeC is made of standard elements only.
Here's the code for all cell types:
TypeA.h
#interface TypeACell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIView *prevRouteView;
#property (strong, nonatomic) IBOutlet UIView *nextRouteView;
#property (strong, nonatomic) IBOutlet UILabel *addressLabel;
#property (strong, nonatomic) IBOutlet EventBadge *eventBadgeView;
#end
TypeB.h
#interface TypeBCell : UITableViewCell
#property (strong, nonatomic) IBOutlet EventBadge *eventBadgeView;
#property (strong, nonatomic) IBOutlet UIView *prevRouteView;
#property (strong, nonatomic) IBOutlet UIView *nextRouteView;
#property (strong, nonatomic) IBOutlet UILabel *titleLabel;
#property (strong, nonatomic) IBOutlet UILabel *addressLabel;
#property (strong, nonatomic) IBOutlet UILabel *startTime;
#property (strong, nonatomic) IBOutlet UILabel *endTime;
#property (strong, nonatomic) IBOutlet CalendarColorView *calendarColor;
#end
TypeC.h
#interface TypeCCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIView *routeView;
#property (strong, nonatomic) IBOutlet UILabel *duration;
#property (strong, nonatomic) IBOutlet UILabel *startTime;
#property (strong, nonatomic) IBOutlet UILabel *endTime;
#property (strong, nonatomic) IBOutlet CalendarColorView *calendarColor;
#property (strong, nonatomic) IBOutlet TransportTypeIconView *transportTypeView;
#end
I choose the type of cell inside cellForRowAtIndexPath method of my ViewController looking at the type of object stored in _tableviewData (the array used to fill the tableView). The code looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([_tableviewData[indexPath.row] isKindOfClass:[EventTypeA class]]){
EventTypeA *event = (EventTypeA *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeACell:event atIndexPath:indexPath];
}
else if([_tableviewData[indexPath.row] isKindOfClass:[EventTypeB class]]) {
EventTypeB *event = (EventTypeB *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeBCell:event atIndexPath:indexPath];
}
else {
EventTypeC *event = (EventTypeC *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeCCell:event atIndexPath:indexPath];
}
}
Inside each method createTypeXCell I work directly on elements and set their properties. Everything is working as expected except properties set on my custom view. So TypeC works perfectly and everything in TypeA and TypeB works as expected except the settings for colors and icons on my eventBadgeView.
The behaviour that I get is that each eventBadgeView, no matter which UITableViewCell belongs to, gets painted with the properties of the last eventBadgeView being worked (the last item of the array).
If I scroll a little bit up or down the UITableView, enough to render one item, that item gets updated well, with the properties I set previously.
But if I scroll too much everything gets messed up once again.
I've noticed that drawRect gets always called a lot later with regards to setNeedsDisplay and I've learned that this is meant to be like this.
I've read on lots of SO posts (I'm not linking here all of them) and based on those what I've tried to do (with no luck) is:
call [cell.eventBadgeView setNeedsDisplay] inside the method that
creates the cell after setting properties
put all the part of setting cell properties and [cell.eventBadgeView setNeedsDisplay] inside dispatch_async
use a CALayer to "force" drawRect to be executed synchronously
Maybe as I'm new to ObjectiveC I'm missing some basic things, and I have big doubts on my custom EventBadge : UIView class since everything else works fine.
Thanks in advance for the help! :)
You should declare those variables out the implementation body otherwise, they'll be threated like gloabal variables in the .m file (more info about this here)
UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;
put them in an interface (inside the .m file or directly in the .h) and declare them as #property
#interface MPEventBadge ()
#property (strong, nonatomic) UIColor *badgeFillColor;
#property (strong, nonatomic) UIColor *badgeBorderColor;
#property (nonatomic) MPInsertEventIcons badgeIcon;
#end
you can then access the variable like
_badgeFillColor = color;
I am not sure is this even possible, but I will ask anyway.
This is my ViewController.h
#interface TBL_GameViewController : UIViewController
#property (strong, nonatomic) IBOutlet UILabel *roundText;
#property (strong, nonatomic) IBOutlet UILabel *roundNumber;
#property (strong, nonatomic) IBOutlet UILabel *playerText;
#property (strong, nonatomic) IBOutlet UILabel *playerScore;
#property (strong, nonatomic) IBOutlet UILabel *computerText;
#property (strong, nonatomic) IBOutlet UILabel *computerScore;
#end
And this is one method from .m file
- (void) lablesHiden:(BOOL)on
{
self.roundText.hidden = on;
self.roundNumber.hidden = on;
self.playerText.hidden = on;
self.playerScore.hidden = on;
self.computerText.hidden = on;
self.computerScore.hidden = on;
}
All this is working file.
Question
is the some way to a access all available labels in my view controller programmatically ?
Reason why I am asking this is:
I will have around 10 methods that will need access these labels, to change various properties (color, text, ...).
If tomorrow I add more label, I will also need add new label to all those methods and I would like to avoid that ?
UPDATE
I the end I used this approach
- (NSArray*) getAllLabels
{
NSArray *labels = [[NSArray alloc] initWithObjects:self.roundText, self.roundNumber, self.playerText, self.playerScore, self.computerText, self.computerScore, nil];
return labels;
}
- (void) appear:(BOOL)on
{
for (UILabel *label in [self getAllLabels]) {
label.alpha = 0.0;
}
// more code
}
There absolutely is a way:
for (id label in self.view.subviews) {
if ([label isKindOfClass:[UILabel class]]) {
// do your stuff...
}
}
You get more granular control about which labels to address by using tags. This is also cleaner than doing class introspection.
For example:
#define kPlayer 100
#define kRound 200
#define kComputer 300
#define kText 10
#define kNumber 20
You assign tags e.g. in viewDidLoad like this:
roundText.tag = kRound + kText;
Now there is no need to iterate through all subviews (you just have one iteration per transaction).
for (int x = 100; x < 400; x += 100) {
for (int y = 10; y < 30; y += 10) {
UILabel *label = (UILabel*) [self.view viewWithTag:x+y];
// do something with label
}
}
You can see that you can very conveniently exclude certain labels if you need to.
Also, via KVC, all labels can be accessed like this:
[self.view.subviews filteredArrayUsingPredicate:
[NSPredicate predicateWithFormat:#"tag > 99"]];
I have a UITableView that covers the whole screen, with a lot of cells.
On the view that holds the UITableView I want to display an UIImage at the top right corner.
This UIImage (or UIImageView) is fixed on the UIView - it is not part of the tableView (in the z order it is over the tableView).
It is possible to let the UITableView float around this image, even when scrolling?
Below an image of my desired structure. Imagine that they are more cells even under der image. The cells (or UILabels) that are covered by the image should be truncated (or do a line break).
Is this possible? Currently I have no idea how to do this.
One option is to adjust the trailing space constraint of a label on your custom table view cell based on tableview's scrollview delegate method. You need to check if your cell is in the area of your imageView and then adjust the constraint. You also need to adjust the constraint in your cellForRowAtIndexPath for the initial layout.
Storyboard
ViewController.m
#import "ViewController.h"
#import "MyCell.h"
#interface ViewController () <UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation ViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 20;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:#"MyCell" forIndexPath:indexPath];
// Configure the cell...
cell.myLabel.text = #"Lorem ipsum dolor sit amet, etc.";
if (indexPath.row >=3 && indexPath.row <= 5) {
cell.trailingSpaceConstraint.constant = 168;
}
return cell;
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
NSUInteger count = 0;
for (MyCell *cell in [self.tableView visibleCells]) {
if (count >= 3 && count <= 6) {
cell.trailingSpaceConstraint.constant = 168.0;
} else {
cell.trailingSpaceConstraint.constant = 20.0;
}
count++;
}
}
#end
MyCell.h
#import <UIKit/UIKit.h>
#interface MyCell : UITableViewCell
#property (weak, nonatomic) IBOutlet UILabel *myLabel;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *trailingSpaceConstraint;
#end
Output
In this sample you need to use custom ViewController, add tableView and imageView, hook tableView's delegate and datasource and outlet. You also need to add custom subclass of UITableViewCell (MyCell) and hook outlets of the cell's label and the trailing space constraint.
The check at scrollViewDidScroll for rows is quick and dirty. Probably a better solution might be to check which cells are touching the imageView rect in indexPathsForRowsInRect.
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.
UPDATED I have a grid (SubViewGrid) that I have drawn on a UIView. I need to be able to prevent the far left column (time) from scrolling horizontally, and the top row (staff name) from scrolling vertically.
The data is drawn in SubViewData, which is within the UIScrollView. I would imagine that I would need two (2) UIScrollViews here, one for the SubViewGrid, the other for the SubViewData. Question is: how to sync them to accomplish the user's needs?
UPDATE Here is the REVISED structure:
#property (nonatomic, weak) IBOutlet UIScrollView *schedScrollView;
#property (nonatomic, weak) IBOutlet UIView * topGridView;
#property (nonatomic, weak) IBOutlet UIView * leftGridView;
#property (nonatomic, weak) IBOutlet UIView * topLeftView; // TODO
See this answer to a similar question.
I think that's how you would need to approach this problem, with the fixed heading(s) in a separate subview to the data. Then set the frame of the fixed subview in scrollViewDidScroll: to create the appearance of a fixed heading at the top (or side) of the scroll view.
For example: Set the initial frame of the header subview to (0, 0, width, height), and then in scrollViewDidScroll: set the frame to (0, contentOffset.y, width, height).
EDIT: Here's an example. In the below screenshot I set up the top row (people), left column (time), and top left cell (to hide the overlapping of the headers) inside a UIScrollView. Then in scrollViewDidScroll: I set the frames of the subviews to fix them to the top, left, and top-left respectively.
ViewController.h:
#interface ViewController : UIViewController <UIScrollViewDelegate>
#property (nonatomic, weak) IBOutlet UIScrollView * theScrollView;
#property (nonatomic, weak) IBOutlet UIImageView * topView;
#property (nonatomic, weak) IBOutlet UIImageView * leftView;
#property (nonatomic, weak) IBOutlet UIView * topLeftView;
#end
ViewController.m:
#import "ViewController.h"
#implementation ViewController
#synthesize theScrollView, topView, leftView, topLeftView;
- (void)viewDidLoad
{
[super viewDidLoad];
self.theScrollView.delegate = self;
}
- (void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.theScrollView.contentSize = CGSizeMake(502, 401);
}
#pragma mark UIScrollViewDelegate methods
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGRect tempFrame = self.topView.frame;
tempFrame.origin.y = scrollView.contentOffset.y;
self.topView.frame = tempFrame;
tempFrame = self.leftView.frame;
tempFrame.origin.x = scrollView.contentOffset.x;
self.leftView.frame = tempFrame;
tempFrame = self.topLeftView.frame;
tempFrame.origin.x = scrollView.contentOffset.x;
tempFrame.origin.y = scrollView.contentOffset.y;
self.topLeftView.frame = tempFrame;
}
#end
And that's all there is to it! Hopefully this helps you.