How interleave value form Items per Row using PBJHexagon Class Flow Layout on my custom UICollectionView?
hi guys I'm Using iOS hexagon grid layout for UICollectionViews PBJHEXAGON
if you look the file .h is like:
#import <UIKit/UIKit.h>
// iOS hexagon grid layout for UICollectionViews
#interface PBJHexagonFlowLayout : UICollectionViewFlowLayout
#property (nonatomic) NSInteger itemsPerRow;
#end
file.m :
#import "PBJHexagonFlowLayout.h"
CG_INLINE CGFloat CGFloat_nearbyint(CGFloat cgfloat) {
#if defined(__LP64__) && __LP64__
return nearbyint(cgfloat);
#else
return nearbyintf(cgfloat);
#endif
}
CG_INLINE CGFloat CGFloat_floor(CGFloat cgfloat) {
#if defined(__LP64__) && __LP64__
return floor(cgfloat);
#else
return floorf(cgfloat);
#endif
}
#interface PBJHexagonFlowLayout ()
{
NSInteger _itemsPerRow;
}
#end
#implementation PBJHexagonFlowLayout
#synthesize itemsPerRow = _itemsPerRow;
#pragma mark - UICollectionViewLayout Subclass hooks
- (void)prepareLayout
{
[super prepareLayout];
if (_itemsPerRow == 0)
_itemsPerRow = 4;
}
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray *layoutAttributes = [[NSMutableArray alloc] init];
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
for (NSInteger i = 0 ; i < numberOfItems; i++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
[layoutAttributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
}
return layoutAttributes;
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger row = (NSInteger) CGFloat_nearbyint( CGFloat_floor(indexPath.row / _itemsPerRow) );
NSInteger col = indexPath.row % _itemsPerRow;
CGFloat horiOffset = ((row % 2) != 0) ? 0 : self.itemSize.width * 0.5f;
CGFloat vertOffset = 0;
UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attributes.size = self.itemSize;
attributes.center = CGPointMake( ( (col * self.itemSize.width) + (0.5f * self.itemSize.width) + horiOffset),
( ( (row * 0.75f) * self.itemSize.height) + (0.5f * self.itemSize.height) + vertOffset) );
return attributes;
}
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return NO;
}
- (CGSize)collectionViewContentSize
{
NSInteger numberOfItems = [self.collectionView numberOfItemsInSection:0];
CGFloat contentWidth = self.collectionView.bounds.size.width;
CGFloat contentHeight = ( ((numberOfItems / _itemsPerRow) * 0.75f) * self.itemSize.height) + (0.5f + self.itemSize.height);
CGSize contentSize = CGSizeMake(contentWidth, contentHeight);
return contentSize;
}
now in my ViewController I have:
ViewController.h :
#import <UIKit/UIKit.h>
#import "PBJHexagonFlowLayout.h"
#import "customCell.h"
#interface ViewController : UIViewController<UICollectionViewDelegate,UICollectionViewDataSource>
{
IBOutlet UICollectionView *collectionView;
}
#end
ViewController.m :
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
PBJHexagonFlowLayout *flowLayout = [[PBJHexagonFlowLayout alloc] init];
flowLayout.scrollDirection = UICollectionViewScrollDirectionVertical;
flowLayout.sectionInset = UIEdgeInsetsMake(3, 3, 3, 3);
flowLayout.minimumInteritemSpacing = 3;
flowLayout.minimumLineSpacing = 3;
flowLayout.headerReferenceSize = CGSizeZero;
flowLayout.footerReferenceSize = CGSizeZero;
flowLayout.itemSize = CGSizeMake(100.0f, 115.0f);
flowLayout.itemsPerRow = 3; // I NEED 3 and 2 Dinamically!! HELP!!
[collectionView setCollectionViewLayout:flowLayout];
//------------------------------------------//
// THANKS GUYS //
// GRETTINGS FROM BOLIVIA //
// ROCK ON!!!! n_n' //
//------------------------------------------//
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
# pragma mark - Collection Functions
-(NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return 18;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"Cell_Bolivia";
customCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
cell.hexagonImage.image = [UIImage imageNamed:#"orangeHexagon.png"];
return cell;
}
#end
I did try to do the test randomly in:
flowLayout.itemsPerRow = [self getRandomValueBetween:2 and:3];
with:
-(int) getRandomValueBetween:(int)lowerBound and:(int)upperBound
{
int rndValue = lowerBound + arc4random() % (upperBound - lowerBound);
return rndValue;
}
but the PBJHexagon Class only show in static way:
How interleave value for Items per Row as shown in the example:
thanks a lot guys!!!
1.Just make the collection view frame wider and move left with half width of the cell.
Then you can set the first and the last cell in the short row with an empty cell.
Here is sample code in Swift
override func viewDidLoad() {
super.viewDidLoad()
//...
flowLayout.itemSize = CGSizeMake(80.0, 92.0)
flowLayout.itemsPerRow = 6 // only 4 item will displayed fully, the first and the last cells will only show half
self.collectionView!.setCollectionViewLayout(flowLayout, animated: false)
self.collectionView!.frame = CGRectMake(-40, 0, self.view.frame.width+80, self.view.frame.height) // move the frame to left with 80/2 and modify the width.
//....
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("hexagon", forIndexPath: indexPath) as! HexagonCell
// set a null image if is the first or last one in short row
// ..............
return cell
}
Related
I am encountering an issue with my UITableView.
The animations for the delete swipe gesture does not work properly.
The thing is, in the new template project "Master Detailed" it works well. But not in the project I am currently working in.
I had an other issue before with the animations that was not working after finishing animate the first time. I fixed it by replacing this in my code.
/// New code
- (void)gl_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
if (!key || !obj) {
return;
}
[self gl_setObject:obj forKeyedSubscript:key];
}
/// Old code
- (void)gl_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
if (!key) {
return;
}
if (!obj) {
obj = [NSNull null];
}
[self gl_setObject:obj forKeyedSubscript:key];
}
And this is the code of the current TableView that is working in the Xcode base project but not in mine.
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
#interface TempViewController: UIViewController
#end
NS_ASSUME_NONNULL_END
#import "TempViewController.h"
#import "TempTableViewCell.h"
#interface TempViewController () <UITableViewDelegate, UITableViewDataSource>
#property (weak, nonatomic) IBOutlet UITableView *tableView;
#property NSMutableArray *objects;
#end
#implementation TempViewController
- (void)viewDidLoad {
[super viewDidLoad];
// TableView
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.rowHeight = 80;
[self.tableView registerNib:[UINib nibWithNibName:#"TempTableViewCell" bundle:nil] forCellReuseIdentifier:#"TempTableViewCell"];
// Add button to add element when deleting too much
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject:)];
self.navigationItem.rightBarButtonItem = addButton;
// Add some data to make the bug work
for (int i = 0; i < 100; i++) {
[self insertNewObject:0];
}
}
- (void)insertNewObject:(id)sender {
if (!self.objects) {
self.objects = [[NSMutableArray alloc] init];
}
[self.objects insertObject:[NSDate date] atIndex:0];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView insertRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
#pragma mark - Table View
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.objects.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TempTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TempTableViewCell" forIndexPath:indexPath];
NSDate *object = self.objects[indexPath.row];
cell.tempLabel.text = [object description];
return cell;
}
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
// Return NO if you do not want the specified item to be editable.
return YES;
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.objects removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
}
}
#end
So, I am wondering guys if you have any clue about where to look at? Or already experienced this issue before?
Thank you for your future help!!
Happy building!
Well,
I went deeper in the direction on our swizzling methods.
I removed this code completely. We were using it to avoid inserting nil value in dictionary. Without it, the bug disappeared. I am not sure why, but if I found the answer someday, I will update this post.
Hope it can save some time for some people!
#import "NSDictionary+NilSafe.h"
#import <objc/runtime.h>
#implementation NSObject (Swizzling)
+ (BOOL)gl_swizzleMethod:(SEL)origSel withMethod:(SEL)altSel {
Method origMethod = class_getInstanceMethod(self, origSel);
Method altMethod = class_getInstanceMethod(self, altSel);
if (!origMethod || !altMethod) {
return NO;
}
class_addMethod(self,
origSel,
class_getMethodImplementation(self, origSel),
method_getTypeEncoding(origMethod));
class_addMethod(self,
altSel,
class_getMethodImplementation(self, altSel),
method_getTypeEncoding(altMethod));
method_exchangeImplementations(class_getInstanceMethod(self, origSel),
class_getInstanceMethod(self, altSel));
return YES;
}
+ (BOOL)gl_swizzleClassMethod:(SEL)origSel withMethod:(SEL)altSel {
return [object_getClass((id)self) gl_swizzleMethod:origSel withMethod:altSel];
}
#end
#implementation NSDictionary (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self gl_swizzleMethod:#selector(initWithObjects:forKeys:count:) withMethod:#selector(gl_initWithObjects:forKeys:count:)];
[self gl_swizzleClassMethod:#selector(dictionaryWithObjects:forKeys:count:) withMethod:#selector(gl_dictionaryWithObjects:forKeys:count:)];
});
}
+ (instancetype)gl_dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt; i++) {
id key = keys[i];
id obj = objects[i];
if (!key) {
continue;
}
if (!obj) {
obj = [NSNull null];
}
safeKeys[j] = key;
safeObjects[j] = obj;
j++;
}
return [self gl_dictionaryWithObjects:safeObjects forKeys:safeKeys count:j];
}
- (instancetype)gl_initWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {
id safeObjects[cnt];
id safeKeys[cnt];
NSUInteger j = 0;
for (NSUInteger i = 0; i < cnt; i++) {
id key = keys[i];
id obj = objects[i];
if (!key) {
continue;
}
if (!obj) {
obj = [NSNull null];
}
safeKeys[j] = key;
safeObjects[j] = obj;
j++;
}
return [self gl_initWithObjects:safeObjects forKeys:safeKeys count:j];
}
#end
#implementation NSMutableDictionary (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = NSClassFromString(#"__NSDictionaryM");
[class gl_swizzleMethod:#selector(setObject:forKey:) withMethod:#selector(gl_setObject:forKey:)];
[class gl_swizzleMethod:#selector(setObject:forKeyedSubscript:) withMethod:#selector(gl_setObject:forKeyedSubscript:)];
});
}
- (void)gl_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (!aKey) {
return;
}
if (!anObject) {
anObject = [NSNull null];
}
[self gl_setObject:anObject forKey:aKey];
}
- (void)gl_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key {
if (!key) {
return;
}
if (!obj) {
obj = [NSNull null];
}
[self gl_setObject:obj forKeyedSubscript:key];
}
#end
#implementation NSNull (NilSafe)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self gl_swizzleMethod:#selector(methodSignatureForSelector:) withMethod:#selector(gl_methodSignatureForSelector:)];
[self gl_swizzleMethod:#selector(forwardInvocation:) withMethod:#selector(gl_forwardInvocation:)];
});
}
- (NSMethodSignature *)gl_methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sig = [self gl_methodSignatureForSelector:aSelector];
if (sig) {
return sig;
}
return [NSMethodSignature signatureWithObjCTypes:#encode(void)];
}
- (void)gl_forwardInvocation:(NSInvocation *)anInvocation {
NSUInteger returnLength = [[anInvocation methodSignature] methodReturnLength];
if (!returnLength) {
// nothing to do
return;
}
// set return value to all zero bits
char buffer[returnLength];
memset(buffer, 0, returnLength);
[anInvocation setReturnValue:buffer];
}
#end
EDIT 1: Well, at the end, the problem was this nil check of obj
if (!key || !obj) {
return;
}
/// Replaced by
if (!key) {
return;
}
I kept the rest of the code, it was producing a crash in case of inserting trying to insert nil value in a NSDictionary.
I don't know what happens, but calling reloadData on the collection view causes nothing. Nothing happens. Maybe I'm missing something, I just don't know what.
I've tried with data on launch and it worked, the collection view was populated. But fetching remote data and then calling reloadData afterwards is not populating the collection view.
Thank you in advance
Class is below:
FoodChannelsCollection.h
#import <UIKit/UIKit.h>
#import "YouTubeDataAPI.h"
#interface FoodChannelsCollection: UIViewController <YouTubeDataAPIDelegate, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource>
#end
FoodChannelsCollection.m
#import "FoodChannelsCollection.h"
#import "ChannelCell.h"
#import "UIImageView+ImageLoader.h"
#interface FoodChannelsCollection()
#property (weak, nonatomic) IBOutlet UICollectionView *collectionView;
#end
#implementation FoodChannelsCollection
//#synthesize collectionView;
NSMutableArray *channels;
//-------------------------------------------------------------------------//
// MARK: Controller's main view setup
//-------------------------------------------------------------------------//
- (void)viewDidLoad
{
[super viewDidLoad];
channels = [NSMutableArray new];
YouTubeDataAPI *dataApi = [YouTubeDataAPI getInstance];
dataApi.delegate = self;
[dataApi fetchFirstChannelsBatch];
}
//-------------------------------------------------------------------------//
// MARK: YouTubeDataAPIDelegate
//-------------------------------------------------------------------------//
- (void)getFirstChannelsBatch:(NSArray *)firstChannelsBatch
{
if (firstChannelsBatch == nil) { return; }
NSLog(#"firstChannelsBatch count: %i", [firstChannelsBatch count]);
if ([firstChannelsBatch count] == 0) { return; }
NSLog(#"firstChannelsBatch count(2): %i", [firstChannelsBatch count]);
[channels addObjectsFromArray: firstChannelsBatch];
NSLog(#"Channels count (Del): %i", [channels count]);
[self.collectionView reloadData];
}
//-------------------------------------------------------------------------//
// MARK: UICollectionViewDelegateFlowLayout
//-------------------------------------------------------------------------//
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
int width = [UIScreen mainScreen].bounds.size.width;
return CGSizeMake(width/2, width/2);
}
- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
return UIEdgeInsetsMake(0.0, 0.0, 0.0, 0.0);
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumLineSpacingForSectionAtIndex:(NSInteger)section
{
return 0;
}
- (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout minimumInteritemSpacingForSectionAtIndex:(NSInteger)section
{
return 0;
}
//-------------------------------------------------------------------------//
// MARK: UICollectionViewDataSource
//-------------------------------------------------------------------------//
- (NSInteger)collectionView:(nonnull UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
NSLog(#"Channels count: %i", [channels count]);
return [channels count];
//return 8;
}
- (nonnull __kindof UICollectionViewCell *)collectionView:(nonnull UICollectionView *)collectionView cellForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
Channel *channel = channels[indexPath.row];
ChannelCell *cell = (ChannelCell *)[collectionView dequeueReusableCellWithReuseIdentifier: #"ChannelCell" forIndexPath: indexPath];
[cell.thumbnail setImageWithUrl: channel.thumbnailUrl];
[cell.channelTitle setText: channel.channelTitle];
//[cell.channelTitle setText: #"Test Cell"];
return cell;
}
//-------------------------------------------------------------------------//
// MARK: Memory Warning
//-------------------------------------------------------------------------//
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
I am developing a document-based application. This document can have multiple pages. so I have an array of NSView objects available with me. Now I want to provide print functionality in this app, but NSPrintOpertion takes only one NSView object so I am not able to generate print preview as well as print off multiple pages of the document.
Is there any way in cocoa to print a multi-page document?
Printing in Cocoa does not come for free, unfortunately. You have to implement your own NSView subclass that handles the drawing. It has to have access to your data and to draw the correct data onto each page, depending on the page number.
Here is a sample source code that can print any NSTableView. I used it here to show how you have to calculate the position of things to draw on the page:
MyPrintView.h:
#import <Cocoa/Cocoa.h>
#interface MyPrintView : NSView {
NSString *printJobTitle;
}
#property (copy, readwrite) NSString *printJobTitle;
- (id)initWithTableView:(NSTableView *)tableToPrint andHeader:(NSString *)header;
#end
MyPrintView.m:
#import "MyPrintView.h"
#interface MyPrintView ()
#property (nonatomic, weak) NSTableView *tableToPrint;
#property (nonatomic, strong) NSString *header;
#property (nonatomic, strong) NSDictionary *attributes;
#property (nonatomic, strong) NSFont *listFont;
#property (nonatomic) float headerHeight;
#property (nonatomic) float footerHeight;
#property (nonatomic) float lineHeight;
#property (nonatomic) float entryHeight;
#property (nonatomic) NSRect pageRect;
#property (nonatomic) int linesPerPage;
#property (nonatomic) int currentPage;
#end
#implementation MyPrintView
#synthesize printJobTitle;
- (id)initWithTableView:(NSTableView *)tableToPrint andHeader:(NSString *)header
{
// Initialize with dummy frame
self = [super initWithFrame:NSMakeRect(0, 0, 700, 700)];
if (self) {
self.tableToPrint = tableToPrint;
self.header = header;
self.listFont = [NSFont fontWithName:#"Helvetica Narrow" size:10.0];
CGFloat x = self.listFont.capHeight;
x = self.listFont.ascender;
x = self.listFont.descender;
self.lineHeight = self.listFont.boundingRectForFont.size.height;
self.entryHeight = [self.listFont capHeight] * 3;
self.headerHeight = 20 + self.entryHeight;
self.footerHeight = 20;
if (self.listFont) {
self.attributes = #{ NSFontAttributeName: self.listFont };
} else {
self.attributes = nil;
}
printJobTitle = #"My Print Job";
}
return self;
}
#pragma mark Pagination
- (BOOL)knowsPageRange:(NSRangePointer)range
{
NSPrintInfo *printInfo = [[NSPrintOperation currentOperation] printInfo];
self.pageRect = [printInfo imageablePageBounds];
NSRect newFrame;
newFrame.origin = NSZeroPoint;
newFrame.size = [printInfo paperSize];
[self setFrame:newFrame];
// Number of lines per page
self.linesPerPage = (self.pageRect.size.height - self.headerHeight - self.footerHeight) / self.entryHeight - 1;
// Number of full pages
NSUInteger noPages = self.tableToPrint.numberOfRows / self.linesPerPage;
// Rest of lines on last page
if (self.tableToPrint.numberOfRows % self.linesPerPage > 0) {
noPages++;
}
range->location = 1;
range->length = noPages;
return YES;
}
- (NSRect)rectForPage:(NSInteger)page
{
self.currentPage = (int)page - 1;
return self.pageRect;
}
- (NSAttributedString *)pageHeader
{
return [[NSAttributedString alloc] initWithString:self.header];
}
#pragma mark Drawing
- (BOOL)isFlipped
{
// Origin top left
return YES;
}
// We need this to find any transformers which are used to display the values of a certain column
static NSValueTransformer *TransformerFromInfoDict( NSDictionary *dict )
{
NSDictionary *options = dict[NSOptionsKey];
if (options == nil) return nil;
NSValueTransformer *transformer = options[NSValueTransformerBindingOption];
if (transformer == nil || (id)transformer == [NSNull null]) {
transformer = nil;
NSString *name = options[NSValueTransformerNameBindingOption];
if (name != nil && (id)name != [NSNull null]) {
transformer = [NSValueTransformer valueTransformerForName: name];
}
}
return transformer;
}
// This is where the drawing takes place
- (void)drawRect:(NSRect)dirtyRect
{
float margin = 20;
float leftMargin = self.pageRect.origin.x + margin;
float topMargin = self.pageRect.origin.y + self.headerHeight;
[NSBezierPath setDefaultLineWidth:0.25];
CGFloat originalWidth = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
originalWidth += col.width;
}
CGFloat widthQuotient = (self.pageRect.size.width - margin) / originalWidth;
CGFloat inset = (self.entryHeight - self.lineHeight - 1.0)/2.0;
// Column titles
CGFloat horOffset = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
NSRect rect = NSMakeRect(linkerRand + horOffset, topMargin, widthQuotient * spalte.width, self.entryHeight);
horOffset += widthQuotient * col.width;
[NSBezierPath strokeRect:rect];
NSString *theTitle = #"--";
if ([col respondsToSelector:#selector(title)]) { // OS X 10.10 and higher
theTitle = col.title;
} else {
NSTableHeaderCell *cell = col.headerCell;
theTitle = cell.title;
}
[theTitle drawInRect:NSInsetRect(rect, inset, inset) withAttributes:self.attributes];
}
NSUInteger firstEntryOfPage = self.currentPage * self.linesPerPage;
NSUInteger lastEntryOfPage = ((self.currentPage + 1) * self.linesPerPage) > self.tableToPrint.numberOfRows ? self.tableToPrint.numberOfRows : ((self.currentPage + 1) * self.linesPerPage);
for (NSUInteger i = 0; i < lastEntryOfPage - firstEntryOfPage; i++) {
#autoreleasepool { // to avoid memory hogging
NSUInteger row = firstEntryOfPage + i;
CGFloat horOffset = 0;
for (NSTableColumn *col in self.tableToPrint.tableColumns) {
NSDictionary *bindingInfo = [spalte infoForBinding: #"value"];
NSArray *columnValues = [bindingInfo[NSObservedObjectKey] valueForKeyPath: bindingInfo[NSObservedKeyPathKey]];
NSString *valueAsStr = #"";
id value = [columnValues objectAtIndex:col];
if ((value != nil) && (![value isKindOfClass:[NSNull class]])) {
// Do we have a transformer for that column? Then transform accordingly.
NSValueTransformer *transformer = TransformerFromInfoDict(bindingInfo);
if (transformer != nil)
value = [transformer transformedValue: value];
if ([value isKindOfClass:[NSString class]]) {
valueAsStr = value;
} else if ([value isKindOfClass:[NSNumber class]]) {
NSCell *cell = [col dataCellForRow:zeile];
if (cell.formatter != nil) {
valueAsStr = [cell.formatter stringForObjectValue:value];
} else {
valueAsStr = [value stringValue];
}
} else {
// We don't know what that is
NSLog(#"value class: %#", [value class]);
valueAsStr = #"????!";
}
}
NSRect rect = NSMakeRect(leftMargin + horOffset, topMargin + (i+1) * self.entryHeight, widthQuotient * col.width, self.entryHeight);
horOffset += widthQuotient * col.width;
// Now we can finally draw the entry
[NSBezierPath strokeRect:rect];
NSRect stringRect = NSInsetRect(rect, inset, inset);
[valueAsStr drawInRect:stringRect withAttributes:self.attributes];
}
}
}
}
#end
This is what you have to include in your document class in printDocumentWithSettings which gets called when the user selects "Print...":
[[self.printInfo dictionary] setValue:#YES forKey:NSPrintHeaderAndFooter];
NSString *headerLine = #"My first printed Table View";
MyPrintView *myPrintView = [[MyPrintView alloc] initWithTableView:theTableView andHeader:headerLine];
NSPrintOperation *op = [NSPrintOperation
printOperationWithView:myPrintView
printInfo:[self printInfo]];
[op setShowsPrintPanel:showPrintPanel];
// Run print operation, which shows the print panel if showPanels was YES
[self runModalPrintOperation:op
delegate:self
didRunSelector:nil
contextInfo:NULL];
I have a custom UICollectionViewLayout class that is exhibiting a weird problem. Screen shots above demonstrate. As the keyboard would obscure the lower fields is they were edited, I wanted to shorten the UICollectionView so that it would not be obscured by the keyboard when it came up. The problem is that I get the result in the right picture. The orange border is the background of the view hosting the UICollectionView, the red is the background color of the UICollectionView. The view with the orange background is the root view for the view controller and is resized with the following (self being the view controller).
CGRect frame = self.view.frame;
frame.size.height -= 300;
self.view.frame = frame;
It indicates that the view gets sized as desired, but for some reason the UICollectionView thinks it does not need to draw the cells in the lower portion. The place where it splits seem to be about consistent but arbitrary. If the original view is scrolled down it does not necessarily split at the section header.
In the layout class, if I return YES for - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds it draws properly but does full redraw so it looks like hell as it does that shifting/fadey thing. And resizing the view does not really invalidate the layout, there is no reason the same layout cannot be used.
Looking for any ideas as to what might be going on. The layout code follows:
//
// MyLayout.h
// uicontroller
//
// Created by Guy Umbright on 8/1/12.
// Copyright (c) 2012 Guy Umbright. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SFNativeLayout.h"
#import "SFLayoutView.h"
#class SFLayout;
#interface SFLayout : UICollectionViewLayout
#property (readonly, strong) SFNativeLayout* sfLayout;
#property (assign) BOOL deferRowsColsToCollectionView;
#property (strong) SFLayoutView* layoutView;
- (id) initWithDictionary:(NSDictionary*) dict;
- (NSInteger) numberOfSections;
- (NSInteger) numberOfItemsInSection:(NSInteger)section;
#end
//
// MyLayout.m
// uicontroller
//
// Created by Guy Umbright on 8/1/12.
// Copyright (c) 2012 Guy Umbright. All rights reserved.
//
#import "SFLayout.h"
#define ROW_HEIGHT 81 //79 with one pixel top bottom
#define HEADER_HEIGHT 30
#interface SFLayout ()
#property (strong) SFNativeLayout* sfLayout;
#property (nonatomic, strong) NSMutableArray* sectionMetrics;
#end
#implementation SFLayout
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (id) initWithDictionary:(NSDictionary*) dict
{
if (self = [super init])
{
self.sfLayout = [[SFNativeLayout alloc] initWithDictionary:dict];
}
return self;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSInteger) numberOfSections
{
if (self.deferRowsColsToCollectionView)
{
return [self.layoutView numberOfSections];
}
else
{
return self.sfLayout.sectionCount;
}
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (BOOL) sectionHasHeader:(NSInteger) section
{
BOOL result = YES;
if (self.deferRowsColsToCollectionView)
{
result = [self.layoutView sectionHasHeader:section];
}
return result;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSInteger) numberOfColumnsInSection:(NSInteger) section
{
if (self.deferRowsColsToCollectionView)
{
return [self.layoutView numberOfColumnsInSection:section];
}
else
{
SFNativeLayoutSection* layoutSection = [self.sfLayout.sections objectAtIndex:section];
NSInteger sectionColumns = layoutSection.columnCount;
return sectionColumns;
}
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSInteger) numberOfRowsInSection:(NSInteger) section
{
if (self.deferRowsColsToCollectionView)
{
return [self.layoutView numberOfRowsInSection:section];
}
else
{
SFNativeLayoutSection* layoutSection = [self.sfLayout.sections objectAtIndex:section];
return layoutSection.rowCount;
}
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (CGFloat) heightForSection:(NSInteger) sectionNdx
{
CGFloat height = 0;
if (self.deferRowsColsToCollectionView)
{
height = [self numberOfRowsInSection:sectionNdx] * ROW_HEIGHT;
if ([self sectionHasHeader:sectionNdx])
{
height += HEADER_HEIGHT;
}
}
else
{
SFNativeLayoutSection* section = [self.sfLayout.sections objectAtIndex:sectionNdx];
height += section.rowCount * ROW_HEIGHT;
if (section.includeHeader)
{
height += HEADER_HEIGHT;
}
}
return height;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (CGSize)collectionViewContentSize
{
BOOL fillSectionMetrics = NO;
CGFloat lastY = 0;
if (self.sectionMetrics == nil)
{
self.sectionMetrics = [NSMutableArray array];
fillSectionMetrics = YES;
}
CGSize sz = [self collectionView].frame.size;
CGFloat height = 0;
for (NSInteger ndx=0; ndx < [self numberOfSections]; ++ndx)
{
CGFloat sectionHeight = [self heightForSection:ndx];
height += sectionHeight;
if (fillSectionMetrics)
{
[self.sectionMetrics addObject:#{#"height":#(sectionHeight),#"startingY":#(lastY),#"endingY":#(lastY+sectionHeight)}];
lastY += sectionHeight;
}
}
sz.height = height;
return sz;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
{
return [super shouldInvalidateLayoutForBoundsChange:newBounds];
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSArray*) attributesForSection:(NSInteger) sectionNdx inRect:(CGRect) rect
{
// NSLog(#"generate attrs for section %d", sectionNdx);
NSMutableArray* result = [NSMutableArray array];
CGRect intersect;
NSDictionary* sectionMetrics = [self.sectionMetrics objectAtIndex:sectionNdx];
SFNativeLayoutSection* layoutSection = [self.sfLayout.sections objectAtIndex:sectionNdx];
NSInteger columnCount = [self numberOfColumnsInSection:sectionNdx];
CGFloat rowStart = [[sectionMetrics valueForKey:#"startingY"] floatValue];
if ((self.layoutView.layoutDatasource != nil) || (layoutSection.includeHeader))
{
CGRect headerFrame = [self collectionView].bounds;
headerFrame.origin.y = rowStart;
headerFrame.size.height = HEADER_HEIGHT;
intersect = CGRectIntersection(rect, headerFrame);
if (!CGRectIsEmpty(intersect))
{
UICollectionViewLayoutAttributes* attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:#"Header"
withIndexPath:[NSIndexPath indexPathForItem:0 inSection:sectionNdx]];
attr.frame = headerFrame;
[result addObject:attr];
}
rowStart = headerFrame.origin.y + headerFrame.size.height;
}
for (int rowNdx = 0; rowNdx < [self numberOfRowsInSection:sectionNdx]; rowNdx++)
{
CGRect rowRect = [self collectionView].frame;
rowRect.size.height = ROW_HEIGHT;
rowRect.origin.y = rowStart + (rowNdx * ROW_HEIGHT);
intersect = CGRectIntersection(rect, rowRect);
if (!CGRectIsEmpty(intersect))
{
NSInteger columns = [self numberOfColumnsInSection:sectionNdx];
for (NSInteger colNdx =0; colNdx < columns; ++colNdx)
{
NSIndexPath* indexPath = [NSIndexPath indexPathForItem:rowNdx * columnCount+colNdx inSection:sectionNdx];
CGRect frame;
frame.origin.y = rowRect.origin.y;
frame.size.height = ROW_HEIGHT;
frame.size.width = self.collectionView.frame.size.width/columnCount;
frame.origin.x = colNdx * frame.size.width;
UICollectionViewLayoutAttributes* attrs = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attrs.frame = frame;
[result addObject:attrs];
}
}
}
return result;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
{
NSMutableArray* attributes = [NSMutableArray array];
for (NSDictionary* sectionMetric in self.sectionMetrics)
{
//can short circuit based on top of section and bottom of rect
CGRect sectionRect = [self collectionView].frame;
sectionRect.origin.y = [[sectionMetric valueForKey:#"startingY"] floatValue];
sectionRect.size.height = [[sectionMetric valueForKey:#"height"] floatValue];
CGRect intersect = CGRectIntersection(rect, sectionRect);
if (!CGRectIsEmpty(intersect))
{
NSArray* sectionAttrs = [self attributesForSection:[self.sectionMetrics indexOfObject:sectionMetric] inRect:intersect];
[attributes addObjectsFromArray:sectionAttrs];
}
}
return attributes;
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [super layoutAttributesForItemAtIndexPath:indexPath];
}
///////////////////////////////////////////////////////////////
//
///////////////////////////////////////////////////////////////
- (NSInteger)numberOfItemsInSection:(NSInteger)section
{
SFNativeLayoutSection* layoutSection = [self.sfLayout.sections objectAtIndex:section];
NSInteger sectionColumns = [self numberOfColumnsInSection:section];
NSInteger sectionRows = layoutSection.rowCount; //%%%
return sectionColumns * sectionRows;
}
#end
I'm delving into the new world of UIPageViewControllers and there are a lot of tutorials out there, however all of them seem to create one view, and then just use new instances of it with different content.
I'd really like to be able to create multiple XIBs and then just chain them together with the UIPageViewController but it's too new and I can't get my head around the way it works.
Well, here's a long answer that you should be able to copy and paste. (This code was adapted from Erica Sadun (https://github.com/erica/iOS-5-Cookbook))
First, create a new class of type UIPageViewController. Call it BookController. Now paste the following code in your .h file.
// Used for storing the most recent book page used
#define DEFAULTS_BOOKPAGE #"BookControllerMostRecentPage"
#protocol BookControllerDelegate <NSObject>
- (id) viewControllerForPage: (int) pageNumber;
#optional
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber;
#end
#interface BookController : UIPageViewController <UIPageViewControllerDelegate, UIPageViewControllerDataSource>
+ (id) bookWithDelegate: (id) theDelegate;
+ (id) rotatableViewController;
- (void) moveToPage: (uint) requestedPage;
- (int) currentPage;
#property (assign) id <BookControllerDelegate> bookDelegate;
#property (nonatomic, assign) uint pageNumber;
and in your .m file:
#define IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
#define SAFE_ADD(_Array_, _Object_) {if (_Object_ && [_Array_ isKindOfClass:[NSMutableArray class]]) [pageControllers addObject:_Object_];}
#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG) (([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? [THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] : nil)
#pragma Utility Class - VC that Rotates
#interface RotatableVC : UIViewController
#end
#implementation RotatableVC
- (void) loadView
{
[super loadView];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view.backgroundColor = [UIColor whiteColor];
}
- (BOOL) shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation
{
return YES;
}
#end
#pragma Book Controller
#implementation BookController
#synthesize bookDelegate, pageNumber;
#pragma mark Debug / Utility
- (int) currentPage
{
int pageCheck = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
return pageCheck;
}
#pragma mark Page Handling
// Update if you'd rather use some other decision style
- (BOOL) useSideBySide: (UIInterfaceOrientation) orientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation);
return isLandscape;
}
// Store the new page and update the delegate
- (void) updatePageTo: (uint) newPageNumber
{
pageNumber = newPageNumber;
[[NSUserDefaults standardUserDefaults] setInteger:pageNumber forKey:DEFAULTS_BOOKPAGE];
[[NSUserDefaults standardUserDefaults] synchronize];
SAFE_PERFORM_WITH_ARG(bookDelegate, #selector(bookControllerDidTurnToPage:), [NSNumber numberWithInt:pageNumber]);
}
// Request controller from delegate
- (UIViewController *) controllerAtPage: (int) aPageNumber
{
if (bookDelegate &&
[bookDelegate respondsToSelector:#selector(viewControllerForPage:)])
{
UIViewController *controller = [bookDelegate viewControllerForPage:aPageNumber];
controller.view.tag = aPageNumber;
return controller;
}
return nil;
}
// Update interface to the given page
- (void) fetchControllersForPage: (uint) requestedPage orientation: (UIInterfaceOrientation) orientation
{
BOOL sideBySide = [self useSideBySide:orientation];
int numberOfPagesNeeded = sideBySide ? 2 : 1;
int currentCount = self.viewControllers.count;
uint leftPage = requestedPage;
if (sideBySide && (leftPage % 2)) leftPage--;
// Only check against current page when count is appropriate
if (currentCount && (currentCount == numberOfPagesNeeded))
{
if (pageNumber == requestedPage) return;
if (pageNumber == leftPage) return;
}
// Decide the prevailing direction by checking the new page against the old
UIPageViewControllerNavigationDirection direction = (requestedPage > pageNumber) ? UIPageViewControllerNavigationDirectionForward : UIPageViewControllerNavigationDirectionReverse;
[self updatePageTo:requestedPage];
// Update the controllers
NSMutableArray *pageControllers = [NSMutableArray array];
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage]);
if (sideBySide)
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage + 1]);
[self setViewControllers:pageControllers direction: direction animated:YES completion:nil];
}
// Entry point for external move request
- (void) moveToPage: (uint) requestedPage
{
[self fetchControllersForPage:requestedPage orientation:(UIInterfaceOrientation)[UIDevice currentDevice].orientation];
}
#pragma mark Data Source
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerAfterViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber + 1];
return [self controllerAtPage:(viewController.view.tag + 1)];
}
- (UIViewController *)pageViewController:(UIPageViewController *)pageViewController viewControllerBeforeViewController:(UIViewController *)viewController
{
[self updatePageTo:pageNumber - 1];
return [self controllerAtPage:(viewController.view.tag - 1)];
}
#pragma mark Delegate
- (UIPageViewControllerSpineLocation)pageViewController:(UIPageViewController *)pageViewController spineLocationForInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSUInteger indexOfCurrentViewController = 0;
if (self.viewControllers.count)
indexOfCurrentViewController = ((UIViewController *)[self.viewControllers objectAtIndex:0]).view.tag;
[self fetchControllersForPage:indexOfCurrentViewController orientation:orientation];
BOOL sideBySide = [self useSideBySide:orientation];
self.doubleSided = sideBySide;
UIPageViewControllerSpineLocation spineLocation = sideBySide ? UIPageViewControllerSpineLocationMid : UIPageViewControllerSpineLocationMin;
return spineLocation;
}
-(void)dealloc{
self.bookDelegate = nil;
}
#pragma mark Class utility routines
// Return a UIViewController that knows how to rotate
+ (id) rotatableViewController
{
UIViewController *vc = [[RotatableVC alloc] init];
return vc;
}
// Return a new book
+ (id) bookWithDelegate: (id) theDelegate
{
BookController *bc = [[BookController alloc] initWithTransitionStyle:UIPageViewControllerTransitionStylePageCurl navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
bc.dataSource = bc;
bc.delegate = bc;
bc.bookDelegate = theDelegate;
return bc;
}
This Class can now be used to control any book you create in any project, and for multiple books in a single project. For each book, create a delegate UIPageViewController with the #interface:
#interface NameOfBookController : UIPageViewController <BookControllerDelegate>
In the .m file of this delegate, include:
// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
[super loadView];
CGRect appRect = [[UIScreen mainScreen] applicationFrame];
self.view = [[UIView alloc] initWithFrame: appRect];
self.view.backgroundColor = [UIColor whiteColor];
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// Establish the page view controller
bookController = [BookController bookWithDelegate:self];
bookController.view.frame = (CGRect){.size = appRect.size};
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
// Add the child controller, and set it to the first page
[self.view addSubview:bookController.view];
[self addChildViewController:bookController];
[bookController didMoveToParentViewController:self];
}
Then add:
- (id) viewControllerForPage: (int) pageNumber
{
// Establish a new controller
UIViewController *controller;
switch (pageNumber) {
case 0:
view1 = [[FirstViewController alloc] init];
view1.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
controller = view1;
//rinse and repeat with each new view controller
break;
case 1:
//etc.
break;
default:
return nil;
break;
}
return controller;
}
For the record, this code is not memory-safe. If using ARC, add in your #autoreleasePool{}; if not, don't forget your retain/release cycle.
I hope this helps!
This article shows how to create an app using UIPageViewController with custom viewcontrollers for each page: http://www.informit.com/articles/article.aspx?p=1760500&seqNum=6