make - xcode ios framework tutorial
Como você carrega UITableViewCells personalizados de arquivos Xib? (16)
A questão é simples: como você carrega o UITableViewCell
personalizado de arquivos Xib? Isso permite que você use o Interface Builder para projetar suas células. A resposta aparentemente não é simples devido a problemas de gerenciamento de memória. Este tópico menciona o problema e sugere uma solução, mas é pré-NDA-release e não possui código. Aqui está um longo tópico que discute o problema sem fornecer uma resposta definitiva.
Aqui está um código que usei:
static NSString *CellIdentifier = @"MyCellIdentifier";
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
cell = (MyCell *)[nib objectAtIndex:0];
}
Para usar esse código, crie MyCell.m / .h, uma nova subclasse de UITableViewCell
e adicione IBOutlets
para os componentes desejados. Em seguida, crie um novo arquivo "Empty XIB". Abra o arquivo Xib no IB, adicione um objeto UITableViewCell
, defina seu identificador como "MyCellIdentifier" e defina sua classe como MyCell e adicione seus componentes. Finalmente, conecte os IBOutlets
aos componentes. Observe que não definimos o proprietário do arquivo no IB.
Outros métodos defendem a configuração do proprietário do arquivo e avisam sobre vazamentos de memória se o Xib não for carregado por meio de uma classe de fábrica adicional. Eu testei o acima em Instrumentos / Vazamentos e não vi vazamentos de memória.
Então, qual é a maneira canônica de carregar células do Xibs? Nós definimos o proprietário do arquivo? Precisamos de uma fábrica? Se sim, como é o código da fábrica? Se existem várias soluções, vamos esclarecer os prós e contras de cada um deles ...
registo
Após o iOS 7, este processo foi simplificado para ( swift 3.0 ):
// For registering nib files
tableView.register(UINib(nibName: "MyCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")
// For registering classes
tableView.register(MyCellClass.self, forCellReuseIdentifier: "cell")
( Nota ) Isso também é possível criando as células nos arquivos
.stroyboard
ou.stroyboard
, como células protótipo. Se você precisar anexar uma classe a eles, você pode selecionar o protótipo da célula e adicionar a classe correspondente (deve ser um descendente doUITableViewCell
, é claro).
Dequeue
E mais tarde, dequeued usando ( swift 3.0 ):
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "Hello"
return cell
}
A diferença é que este novo método não só extrai a célula, mas também cria se não existir (isso significa que você não precisa fazer if (cell == nil)
shenanigans), e a célula está pronta para usar da mesma forma que no exemplo acima.
( Atenção )
tableView.dequeueReusableCell(withIdentifier:for:)
tem o novo comportamento, se você chamar o outro (semindexPath:
você obtém o comportamento antigo, no qual você precisa verificar pornil
e por instância você mesmo, observe oUITableViewCell?
valor de retorno.
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCellClass
{
// Cell be casted properly
cell.myCustomProperty = true
}
else
{
// Wrong type? Wrong identifier?
}
E, claro, o tipo da classe associada da célula é aquele que você definiu no arquivo .xib para a subclasse UITableViewCell
ou, alternativamente, usando o outro método de registro.
Configuração
Idealmente, suas células já foram configuradas em termos de aparência e posicionamento de conteúdo (como rótulos e visualizações de imagens) no momento em que você as registrou, e no método cellForRowAtIndexPath
você simplesmente as preenche.
Todos juntos
class MyCell : UITableViewCell
{
// Can be either created manually, or loaded from a nib with prototypes
@IBOutlet weak var labelSomething : UILabel? = nil
}
class MasterViewController: UITableViewController
{
var data = ["Hello", "World", "Kinda", "Cliche", "Though"]
// Register
override func viewDidLoad()
{
super.viewDidLoad()
tableView.register(MyCell.self, forCellReuseIdentifier: "mycell")
// or the nib alternative
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return data.count
}
// Dequeue
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyCell
cell.labelSomething?.text = data[indexPath.row]
return cell
}
}
E, claro, tudo isso está disponível na ObjC com os mesmos nomes.
Crie sua própria classe personalizada Subclasse
AbcViewCell
deUITableViewCell
(Certifique-se de que o nome do arquivo da classe e o nome do arquivo da ponta sejam os mesmos)Crie este método de classe de extensão.
extension UITableViewCell { class func fromNib<T : UITableViewCell>() -> T { return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T } }
Use-o.
let cell: AbcViewCell = UITableViewCell.fromNib()
A solução certa é esta:
- (void)viewDidLoad
{
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:@"ItemCell" bundle:nil];
[[self tableView] registerNib:nib forCellReuseIdentifier:@"ItemCell"];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create an instance of ItemCell
PointsItemCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ItemCell"];
return cell;
}
Aqui está o método de classe que tenho usado para criar células personalizadas a partir de XIBs:
+ (CustomCell*) createNewCustomCellFromNib {
NSArray* nibContents = [[NSBundle mainBundle]
loadNibNamed:@"CustomCell" owner:self options:NULL];
NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
CustomCell *customCell= nil;
NSObject* nibItem = nil;
while ( (nibItem = [nibEnumerator nextObject]) != nil) {
if ( [nibItem isKindOfClass: [CustomCell class]]) {
customCell = (CustomCell*) nibItem;
if ([customCell.reuseIdentifier isEqualToString: @"CustomCell"]) {
break; // we have a winner
}
else
fuelEntryCell = nil;
}
}
return customCell;
}
Em seguida, no XIB, defino o nome da classe e reutilizo o identificador. Depois disso, posso chamar esse método no meu controlador de visualização em vez do
[[UITableViewCell] alloc] initWithFrame:]
É bastante rápido o suficiente e usado em dois dos meus aplicativos de remessa. É mais confiável do que chamar [nib objectAtIndex:0]
, e na minha opinião, pelo menos, mais confiável do que o exemplo de Stephan Burlot, porque você tem a garantia de obter apenas uma visualização de um XIB que seja do tipo certo.
Aqui está uma abordagem universal para registrar células no UITableView
:
protocol Reusable {
static var reuseID: String { get }
}
extension Reusable {
static var reuseID: String {
return String(describing: self)
}
}
extension UITableViewCell: Reusable { }
extension UITableView {
func register<T: UITableViewCell>(cellClass: T.Type = T.self) {
let bundle = Bundle(for: cellClass.self)
if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil {
let nib = UINib(nibName: cellClass.reuseID, bundle: bundle)
register(nib, forCellReuseIdentifier: cellClass.reuseID)
} else {
register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID)
}
}
Explicação:
-
Reusable
protocoloReusable
gera o ID da célula a partir do nome da classe. Certifique-se de seguir a convenção:cell ID == class name == nib name
. -
UITableViewCell
está em conformidade com o protocoloReusable
. -
UITableView
extensãoUITableView
abstrai a diferença no registro de células via bico ou classe.
Exemplo de uso:
override func viewDidLoad() {
super.viewDidLoad()
let tableView = UITableView()
let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
cellClasses.forEach(tableView.register)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
...
return cell
}
Aqui estão dois métodos que o autor original afirma ter sido recomendado por um engenheiro da IB .
Veja a postagem atual para mais detalhes. Eu prefiro o método # 2 como parece mais simples.
Método 1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
if (cell == nil) {
// Create a temporary UIViewController to instantiate the custom cell.
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (BDCustomCell *)temporaryController.view;
[[cell retain] autorelease];
// Release the temporary UIViewController.
[temporaryController release];
}
return cell;
}
Método 2:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
if (cell == nil) {
// Load the top-level objects from the custom cell XIB.
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
// Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
cell = [topLevelObjects objectAtIndex:0];
}
return cell;
}
Atualização (2014): o método 2 ainda é válido, mas não há mais documentação para ele. Ela costumava estar nos documentos oficiais, mas agora é removida em favor de storyboards.
Eu postei um exemplo de trabalho no Github:
https://github.com/bentford/NibTableCellExample
Eu decidi postar desde que eu não gosto de nenhuma dessas respostas - as coisas sempre podem ser mais simples e esta é de longe a maneira mais concisa que eu encontrei.
1. Construa seu Xib no Interface Builder como você gosta
- Defina o proprietário do arquivo para a classe NSObject
- Adicione um UITableViewCell e defina sua classe para MyTableViewCellSubclass - se o seu IB travar (acontece no Xcode> 4 até o momento da redação deste texto), basta usar um UIView para fazer a interface no Xcode 4 se você ainda tiver
- Elabore suas subvisualizações dentro desta célula e anexe suas conexões IBOutlet à sua @interface no .h ou .m (.m é minha preferência)
2. Na sua subclasse UIViewController ou UITableViewController
@implementation ViewController
static NSString *cellIdentifier = @"MyCellIdentier";
- (void) viewDidLoad {
...
[self.tableView registerNib:[UINib nibWithNibName:@"MyTableViewCellSubclass" bundle:nil] forCellReuseIdentifier:cellIdentifier];
}
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCellSubclass *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
...
return cell;
}
3. Na sua MyTableViewCellSubclass
- (id) initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
...
}
return self;
}
Eu não sei se existe uma maneira canônica, mas aqui está o meu método:
- Criar um xib para um ViewController
- Definir a classe do proprietário do arquivo para UIViewController
- Exclua a visualização e adicione um UITableViewCell
- Defina a classe do seu UITableViewCell para sua classe personalizada
- Definir o identificador do seu UITableViewCell
- Defina a saída da visualização do controlador de exibição para o seu UITableViewCell
E use este código:
MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
cell = (MyCustomViewCell *)c.view;
[c release];
}
No seu exemplo, usando
[nib objectAtIndex:0]
pode quebrar se a Apple alterar a ordem dos itens no xib.
Marque isto - http://eppz.eu/blog/custom-uitableview-cell/ - maneira realmente conveniente usando uma pequena classe que termina uma linha na implementação do controlador:
-(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath
{
return [TCItemCell cellForTableView:tableView
atIndexPath:indexPath
withModelSource:self];
}
No Swift 4.2 e no Xcode 10
Eu tenho três arquivos de célula XIB
em ViewDidLoad registre seus arquivos XIB como este ...
Esta é a primeira abordagem
tableView.register(UINib.init(nibName: "XIBCell", bundle: nil), forCellReuseIdentifier: "cell1")
tableView.register(UINib.init(nibName: "XIBCell2", bundle: nil), forCellReuseIdentifier: "cell2")
//tableView.register(UINib.init(nibName: "XIBCell3", bundle: nil), forCellReuseIdentifier: "cell3")
A segunda abordagem registra diretamente os arquivos XIB no cellPortRowAt indexPath:
Esta é a minha função de delegação tableview
//MARK: - Tableview delegates
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 6
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//This is first approach
if indexPath.row == 0 {//Load first XIB cell
let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell1") as! XIBCell
return placeCell
//Second approach
} else if indexPath.row == 5 {//Load XIB cell3
var cell = tableView.dequeueReusableCell(withIdentifier:"cell3") as? XIBCell3
if cell == nil{
let arrNib:Array = Bundle.main.loadNibNamed("XIBCell3",owner: self, options: nil)!
cell = arrNib.first as? XIBCell3
}
//ADD action to XIB cell button
cell?.btn.tag = indexPath.row//Add tag to button
cell?.btn.addTarget(self, action: #selector(self.bookbtn1(_:)), for: .touchUpInside);//selector
return cell!
//This is first approach
} else {//Load XIB cell2
let placeCell = tableView.dequeueReusableCell(withIdentifier: "cell2") as! XIBCell2
return placeCell
}
}
O que eu faço para isso é declarar uma IBOutlet UITableViewCell *cell
em sua classe controladora. Em seguida, invoque o método de classe NSBundle loadNibNamed
, que alimentará o UITableViewCell
para a célula declarada acima.
Para o xib, vou criar um xib vazio e adicionar o objeto UITableViewCell
no IB, onde ele pode ser configurado conforme necessário. Essa visão é então conectada ao IBOutlet
da célula na classe do controlador.
- (UITableViewCell *)tableView:(UITableView *)table
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"%@ loading RTEditableCell.xib", [self description] );
static NSString *MyIdentifier = @"editableCellIdentifier";
cell = [table dequeueReusableCellWithIdentifier:MyIdentifier];
if(cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"RTEditableCell"
owner:self
options:nil];
}
return cell;
}
Primeiro, importe seu arquivo de célula personalizado #import "CustomCell.h"
e, em seguida, altere o método delegado conforme abaixo mencionado:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = @"CustomCell";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}
return cell;
}
Se você estiver usando o Interface Builder para criar células, verifique se definiu o Identificador no Inspetor. Em seguida, verifique se é o mesmo ao chamar dequeueReusableCellWithIdentifier.
Eu acidentalmente esqueci de definir alguns identificadores em um projeto com muita mesa, e a mudança de desempenho foi como dia e noite.
Solução Correta é isso
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"CustomCell"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
return cell;
}
NSString *CellIdentifier = [NSString stringWithFormat:@"cell %ld %ld",(long)indexPath.row,(long)indexPath.section];
NewsFeedCell *cell = (NewsFeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell=nil;
if (cell == nil)
{
NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"NewsFeedCell" owner:nil options:nil];
for(id currentObject in topLevelObjects)
{
if([currentObject isKindOfClass:[NewsFeedCell class]])
{
cell = (NewsFeedCell *)currentObject;
break;
}
}
}
return cell;
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellReuseIdentifier = "collabCell"
var cell:collabCell! = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as? collabCell
if cell == nil {
tableView.register(UINib(nibName: "collabCell", bundle: nil), forCellReuseIdentifier: cellReuseIdentifier)
cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! collabCell!
}
return cell
}