UICollectionViewCell Border / Shadow - objective-c

When building an iPad App, how can you draw a border around a UICollectionViewCell?
More details: I implemented a class ProductCell which extends UICollectionViewCell. Now, I would like to assign some fancy details, e.g. a border, shadow, etc. However, when trying to use something like this here, Xcode tells me that the receiver type 'CALayer' is a forward declaration.

Just for a bit more implementation:
#import <QuartzCore/QuartzCore.h>
in your.m
Make sure your class implements
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath;
as this is where the cell is setup.
You can then change cell.layer.background (only available once quartz is imported)
See below
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCollectionViewCell *cell = [cv dequeueReusableCellWithReuseIdentifier:#"pressieCell" forIndexPath:indexPath];
//other cell setup here
cell.layer.borderWidth=1.0f;
cell.layer.borderColor=[UIColor blueColor].CGColor;
return cell;
}

Swift
Updated for Swift 3
Assuming you have your Collection View set up with the required methods, you can just write a few lines of code to add the border.
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath as IndexPath) as! MyCollectionViewCell
cell.myLabel.text = self.items[indexPath.item]
cell.backgroundColor = UIColor.cyan
// add a border
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8 // optional
return cell
}
Notes
It is not necessary to import QuartzCore in Swift if you have already imported UIKit.
If you also want to add shadow, then see this answer.

You need to include the framework QuartzCore and import the header into your class:
#import <QuartzCore/QuartzCore.h>

Swift 4
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
Add it in datasource method, after creating the cell
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CollectionViewCell", for: indexPath)
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
}

I think it is better to add this config into your custom cell implementation, not in datasource delegate method.
cell.layer.borderColor = UIColor.black.cgColor
cell.layer.borderWidth = 1
cell.layer.cornerRadius = 8 // optional

Related

Create UICollectiionViewCell based on UIView

I am trying to have a UICollectionView that holds different UIViews as its cells. Is this possible or do I have to make them UICollectionViewCells?
I don't have an objective-c example, but you should be able to get the concept from the code example below.
An example how you can create a cell that wraps a UIView and is more reusable
class ProfileView: UIView {
var imageView: UIImageView!
var name: UILabel!
}
class ProfileCollectionViewCell: UICollectionViewCell {
let profileView = ProfileView()
init() {
super.init()
configureConstraints()
}
func configureConstraints() {
// use a handy extension you've already built
contentView.addSubView(profileView)
profileView.pinToEdges(of: contentView)
}
}
func collectionView(_ collectionView: UICollectionView,
cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let row = self.objects[indexPath.row]
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "someId" for: indexPath) as? ProfileCollectionViewCell
cell?.profileView.imageView.image = row["image"]
cell?.profileView.name.text = row["name"]
return cell
}
note: you may need to manage 'resetting the cells state' before it gets reused with something like:
override prepareForReuse() {
super.prepareForReuse()
profileView.imageView.image = nil
profileView.name.text = ""
}
You have to return UICollectionViewCells. UICollectionView don't accept UIViews.
What you can do is create a generic UICollectionViewCell that can embed any UIView.
The reason is because collection view cells have specific composition for layout and recycling.
Also, you add child in your UIView directly on the view itself, collection view cells have a contentView, like UITableViewCells.

Using 3D touch peek and pop with Longpress

I'm trying to emulate the 3D touch peek and pop for all devices (like Instagram) - so show the peek and pop at a longpress gesture. Is this possible? I found PeekView but so far I'm finding it impossible to incorporate this with my Objective C app (it's in Swift).
Currently I have replicated the PeekView Project - I have a collection view with a few cells and each cell has an image in it.
#property (nonatomic, strong) UILongPressGestureRecognizer *longPress;
- (PhotoCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
PhotoCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:kPhotoCell forIndexPath:indexPath];
cell.image.image = [UIImage imageNamed:#"image1"];
return cell;
}
- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(nonnull UICollectionViewCell *)cell forItemAtIndexPath:(nonnull NSIndexPath *)indexPath {
self.longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:#selector(handleLongPressGestures:)];
self.longPress.minimumPressDuration = 1.0f;
self.longPress.allowableMovement = 100.0f;
[cell addGestureRecognizer:self.longPress];
}
Now I'm trying to implement the handleLongPressGestures method but I can't use the PeekView methods due to the error shown below.
edit:
So when trying to use the viewForController method as shown in the example, if I add #objc, it complains about the Options part. Is there a workaround?
public enum PeekViewActionStyle : Int {
case Default
case Selected
case Destructive
}
public struct PeekViewAction {
var title: String
var style: PeekViewActionStyle
}
public class PeekView: UIView {
var shouldToggleHidingStatusBar = false
var contentView: UIView?
var buttonHolderView: UIView?
var completionHandler: (Int -> Void)?
var arrowImageView: UIImageView!
public class func viewForController(
parentViewController parentController: UIViewController,
contentViewController contentController: UIViewController,
expectedContentViewFrame frame: CGRect,
fromGesture gesture: UILongPressGestureRecognizer,
shouldHideStatusBar flag: Bool,
withOptions menuOptions: [PeekViewAction]?=nil,
completionHandler handler: (Int -> Void)?=nil) {
...
The problem with the library is that Swift struct cannot be bridged to Objective-C, so you cannot create the PeekViewAction array. I have updated the code with Obj-C support so now you can use another static method to show the PeekView. The usage is as below:
NSArray *options = #[#{#"Option 1": #(PeekViewActionStyleDefault)},
#{#"Option 2": #(PeekViewActionStyleDestructive)}];
UIViewController *contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"previewVC"];
[PeekView viewForControllerWithParentViewController:self
contentViewController:contentViewController
expectedContentViewFrame:CGRectMake(0, 0, 280, 400)
fromGesture:gesture
shouldHideStatusBar:YES
withOptions:options
completionHandler:nil];
Make sure you download the latest version of the library. Thanks for using PeekView and please report back if there's any other issue coming up :)

UITableView titleForHeaderInSection shows all caps

I am using titleForHeaderInSection to show a header for a UITableView section. It worked fine with the iOS6 SDK, but the iOS7 SDK shows the header in all CAPS.
I guess it's part of Apple's updated Human Interface Guidelines; all examples in there show headers in all caps. Also, all section headers in Settings on my iPhone are in all caps.
However, I wonder if there is a way around that. Normally, I wouldn't mind showing caps if that improves consistency, but when I need to show people's names in a section header, it's a bit awkward.
Anybody any idea how to get around to capitalization?
Yes, we had very much a similar problem to this, and my own solution was as follows:
The Apple UITableViewHeaderFooterView documentation (the link to it is irritatingly long but you can find it easily with your favourite search engine) says you can access the textLabel of the header view without having to format your own view via viewForHeaderInSection method.
textLabel A primary text label for the view. (read-only)
#property(nonatomic, readonly, retain) UILabel *textLabel Discussion
Accessing the value in this property causes the view to create a
default label for displaying a detail text string. If you are managing
the content of the view yourself by adding subviews to the contentView
property, you should not access this property.
The label is sized to fit the content view area in the best way
possible based on the size of the string. Its size is also adjusted
depending on whether there is a detail text label present.
With some additional searching the best place to modify the label text was the willDisplayHeaderView method (suggested in How to implement `viewForHeaderInSection` on iOS7 style?).
So, the solution I came up with is pretty simple and just does a transformation of the text label string, after it's actually been set by titleForHeaderInSection:
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
//I have a static list of section titles in SECTION_ARRAY for reference.
//Obviously your own section title code handles things differently to me.
return [SECTION_ARRAY objectAtIndex:section];
}
and then simply call the willDisplayHeaderView method to modify how it looks:
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
if([view isKindOfClass:[UITableViewHeaderFooterView class]]){
UITableViewHeaderFooterView *tableViewHeaderFooterView = (UITableViewHeaderFooterView *) view;
tableViewHeaderFooterView.textLabel.text = [tableViewHeaderFooterView.textLabel.text capitalizedString];
}
}
You can put in your own 'if' or 'switch' clauses in there as the section number is also passed to the method, so hopefully it'll allow you to show your user/client name in capitalised words selectively.
solution which i found is add Title in "titleForHeaderInSection" method
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return #"Table Title";
}
and then call the willDisplayHeaderView method to Update :
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
header.textLabel.textColor = [UIColor darkGrayColor];
header.textLabel.font = [UIFont boldSystemFontOfSize:18];
CGRect headerFrame = header.frame;
header.textLabel.frame = headerFrame;
header.textLabel.text= #"Table Title";
header.textLabel.textAlignment = NSTextAlignmentLeft;
}
In Swift,
override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let headerView = view as! UITableViewHeaderFooterView
headerView.textLabel.text = "My_String"
}
If you want to just keep the string as it is, you could simply do this:
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section {
if ([view isKindOfClass:[UITableViewHeaderFooterView class]] && [self respondsToSelector:#selector(tableView:titleForHeaderInSection:)]) {
UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView *)view;
headerView.textLabel.text = [self tableView:tableView titleForHeaderInSection:section];
}
}
Swift solution:
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
if let headerView = view as? UITableViewHeaderFooterView, self.respondsToSelector(Selector("tableView:titleForHeaderInSection:")) {
headerView.textLabel?.text = self.tableView(tableView, titleForHeaderInSection: section)
}
}
One solution I found is to utilize UITableViewHeaderFooterView.
Instead of
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return #"some title";
}
Do
- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *identifier = #"defaultHeader";
UITableViewHeaderFooterView *headerView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:identifier];
if (!headerView) {
headerView = [[UITableViewHeaderFooterView alloc] initWithReuseIdentifier:identifier];
}
headerView.textLabel.text = #"some title";
return headerView;
}
The annoying downside is that the table view will no longer automatically adjust the section header height for you. So if your header heights varies, you'll have to do something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
id headerAppearance = [UILabel appearanceWhenContainedIn:[UITableViewHeaderFooterView class], nil];
UIFont *font = [headerAppearance font];
CGFloat viewWidth = CGRectGetWidth(tableView.frame);
CGFloat xInset = 10;
CGFloat yInset = 5;
CGFloat labelWidth = viewWidth - xInset * 2;
CGSize size = [sectionInfo.name sizeWithFont:font constrainedToSize:CGSizeMake(labelWidth, MAXFLOAT)];
return size.height + yInset * 2;
}
I really don't like hard-coding layout information (the inset) this way, as it might break in the future version. If anyone has a better solution to get/set the header height, I'm all ears.
Thanks #Animal451. This is more generic solution that would work with any type of header string.
// We need to return the actual text so the header height gets correctly calculated
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.headerString;
}
- (void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
{
UITableViewHeaderFooterView *header = (UITableViewHeaderFooterView *)view;
[header.textLabel setText:self.headerString]; //String in the header becomes uppercase. Workaround to avoid that.
}
To avoid duplication the header string can be declared anywhere else. I have done it in the -viewDidLoad method.
In addition to #Animal451's post.
For swift 3 you can use
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard section == 0 ,
let tableViewHeaderFooterView = view as? UITableViewHeaderFooterView
else { return }
tableViewHeaderFooterView.textLabel?.text = "Your awesome string"
}
And then ignore - titleForHeaderInSection:
Keep in mind that this code is for 1st section only. If you want to go through all of your sections, you'll need to add support for them
Swift 5
Static TableViewController & storyboard section title.
The below code will work the first letter capitalized.
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let titleView = view as! UITableViewHeaderFooterView
titleView.textLabel?.text = titleView.textLabel?.text?.capitalized
}
Simplest Solution for was below: Swift 2.x
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
header.textLabel?.text = header.textLabel?.text?.capitalizedString
}
Swift 3.x:
if let headerView = view as? UITableViewHeaderFooterView {
headerView.textLabel?.text? = headerView.textLabel?.text?.capitalized ?? ""
}
The solution which worked for me was to create a simple UILabel Instance Variable to use as the section header view. (Your needs may be different, but for me, I only needed to have text in my header...)
Then, inside viewForHeaderInSection, I set up the label as required, and return this label as the header view.
Then, whenever I want to update the text in the header, I simply change the label's text directly.
In the .h file:
#property (nonatomic, retain) UILabel *sectionHeaderLabel;
then in the .m file:
- (UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
return [self refreshMySectionHeaderLabel]; // I created this handy little method to update the header text
}
// Get the latest text for the section header...
-(UILabel *) refreshMySectionHeaderLabel {
// Initialise the first time.
if( sectionHeaderLabel == nil ) {
sectionHeaderLabel = [[UILabel alloc] initWithFrame: CGRectNull];
}
// ...
// May also set other attributes here (e.g. colour, font, etc...)
// Figure out the text...
sectionHeaderLabel.text = #"Your updated text here...";
return sectionHeaderLabel;
}
When I want to update my section header, I simply call:
[self refreshMySectionHeaderLabel];
...or you could simply call:
sectionHeaderLabel.text = #"The new text...";
Take note: I only have 1 section (section 0). You may need to account for multiple sections...
One liner solution, based on #Dheeraj D answer:
override func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
(view as? UITableViewHeaderFooterView)?.textLabel?.text = (view as? UITableViewHeaderFooterView)?.textLabel?.text?.capitalized
}
SWIFT
In implementation, i found out that you need to specify section header text in both titleForHeaderInSection & willDisplayHeaderView function, othervise header hides.
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let sectionHeader = datasource[section].sectionHeader
// Header Title
return sectionHeader
}
func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let header = view as? UITableViewHeaderFooterView else { return }
header.textLabel?.textColor = UIColor.darkGray
header.textLabel?.font = UIFont.systemFont(ofSize: 20, weight: .semibold)
header.textLabel?.frame = header.frame
// Header Title
header.textLabel?.text = datasource[section].sectionHeader
}
}
Use the method viewForHeaderInSection to create the header title.
In viewDidLoad register a UITableViewHeaderFooterView
tableView.register(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "HeaderFooterViewID")
add delegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "HeaderFooterViewID")
var config = header?.defaultContentConfiguration()
let title = "My Header Title"
let attribute = [ NSAttributedString.Key.foregroundColor: UIColor.blue ]
config?.attributedText = NSAttributedString(string: title, attributes: attribute)
header?.contentConfiguration = config
return header
}
You can adjust the attributed text more, reference
With RubyMotion / RedPotion, paste this into your TableScreen:
def tableView(_, willDisplayHeaderView: view, forSection: section)
view.textLabel.text = self.tableView(_, titleForHeaderInSection: section)
end
I think what's happening is that somewhere in there the text gets set to ALL CAPS. This little trick RESETS the text back to whatever it was originally. Works like a charm for me!

Reordering cells in UITableView

I have a tableView with 10 sections and each section has 3 rows.
Is it possible to reoder the section using the tableViewDelegate methods
-(BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
return YES;
}
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
toIndexPath:(NSIndexPath *)toIndexPath {
}
Right now if i am dragging a row from one section and dropping it on another section, its being added to that section. The footer view is also not being selected for reordering, and it remains in the original section.
Any ideas?
Thanks
Since this comes up as one of the first results in Google I thought I'd add the swift code. The Objective-C will is almost the same except for the name of the indexPaths in moveRowAtIndex.
Enable editing mode:
If you're using a UITableViewController you toggle editing mode with:
func toggleEditing() {
self.editing = !self.editing
}
If you're not using a UITableViewController (e.g. you have added a table view to a UIViewController (or any View Controller other than UITableViewController) then you toggle it with:
func toggleEditing() {
self.editing = !self.editing
}
Note: tableView is the name of your UITableView.
Implement canMoveRowAtIndexPath
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
Implement moveRowAtIndexPath
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
var itemToMove = tableData[fromIndexPath.row]
tableData.removeAtIndex(fromIndexPath.row)
tableData.insert(itemToMove, atIndex: toIndexPath.row)
}
Check out this tutorial: Add, Delete & Reorder UITableView Rows. There's a working project at the end of the tutorial.

How to simply fill a TableView for iPhone

I have got an UITableView. How to simply fill it with three elements, for example "e1", "e2", "e3" ?
set DataSource of your table to your class and define in your class 3 methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellId = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: cellId];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:cellId] autorelease];
}
[cell setText:[NSString stringWithFormat:#"e%i",indexPath:[indexPath row]];
return cell;
}
UITableView is a component that rewards study. Read the local doc pages on UITableView and its datasource and delegate protocols.
There is a table view programming guide in the docs as well. It is a good read.
Some of it took me a while to grok and I am still refining my approach each time I implement one.
Using the API and implementing the methods it requires, you should be able to bend it to your will.
Hellra1ser's example should push you in the right direction. Lookup those methods first, you will be using them alot.
Code in swift 3.1:
Just in case if someone needs to copy paste it for the purpose of testing.
extension ViewController : UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 50
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Test"
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 60
}
}