it-swarm.com.de

Entwerfen Sie den Abschnittsheader von UITableView im Interface Builder

Ich habe eine xib-Datei mit einer UITableView, für die ich eine benutzerdefinierte Header-Ansicht mit der Delegat-Methode tableView:viewForHeaderInSection: hinzufügen möchte. Gibt es eine Möglichkeit, es in Interface Builder zu entwerfen und dann einige Eigenschaften seiner Unteransicht programmgesteuert zu ändern?

Meine UITableView hat mehr Abschnittsheader, so dass die Erstellung einer UIView in Interface Builder und deren Rückgabe nicht funktioniert, da ich sie duplizieren müsste, aber es gibt keine gute Methode dafür. Das Archivieren und Aufheben der Archivierung funktioniert für UIImages nicht, sodass UIImageViews leer angezeigt wird.

Ich möchte sie auch nicht programmgesteuert erstellen, da sie zu komplex sind und der resultierende Code schwer zu lesen und zu verwalten ist.

Edit 1 : Hier ist meine tableView:viewForHeaderInSection:-Methode:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    CGSize headerSize = CGSizeMake(self.view.frame.size.width, 100);

    /* wrapper */

    UIView *wrapperView = [UIView viewWithSize:headerSize];

    wrapperView.backgroundColor = [UIColor colorWithHexString:@"2670ce"];

    /* title */

    CGPoint titleMargin = CGPointMake(15, 8);

    UILabel *titleLabel = [UILabel labelWithText:self.categoriesNames[section] andFrame:CGEasyRectMake(titleMargin, CGSizeMake(headerSize.width - titleMargin.x * 2, 20))];

    titleLabel.textColor = [UIColor whiteColor];
    titleLabel.font = [UIFont fontWithStyle:FontStyleRegular andSize:14];

    [wrapperView addSubview:titleLabel];

    /* body wrapper */

    CGPoint bodyWrapperMargin = CGPointMake(10, 8);

    CGPoint bodyWrapperViewOrigin = CGPointMake(bodyWrapperMargin.x, CGRectGetMaxY(titleLabel.frame) + bodyWrapperMargin.y);
    CGSize bodyWrapperViewSize = CGSizeMake(headerSize.width - bodyWrapperMargin.x * 2, headerSize.height - bodyWrapperViewOrigin.y - bodyWrapperMargin.y);

    UIView *bodyWrapperView = [UIView viewWithFrame:CGEasyRectMake(bodyWrapperViewOrigin, bodyWrapperViewSize)];

    [wrapperView addSubview:bodyWrapperView];

    /* image */

    NSInteger imageSize = 56;
    NSString *imageName = [self getCategoryResourceItem:section + 1][@"image"];

    UIImageView *imageView = [UIImageView imageViewWithImage:[UIImage imageNamed:imageName] andFrame:CGEasyRectMake(CGPointZero, CGEqualSizeMake(imageSize))];

    imageView.layer.masksToBounds = YES;
    imageView.layer.cornerRadius = imageSize / 2;

    [bodyWrapperView addSubview:imageView];

    /* labels */

    NSInteger labelsWidth = 60;

    UILabel *firstLabel = [UILabel labelWithText:@"first" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 0, labelsWidth, 16)];

    [bodyWrapperView addSubview:firstLabel];

    UILabel *secondLabel = [UILabel labelWithText:@"second" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 20, labelsWidth, 16)];

    [bodyWrapperView addSubview:secondLabel];

    UILabel *thirdLabel = [UILabel labelWithText:@"third" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 40, labelsWidth, 16)];

    [bodyWrapperView addSubview:thirdLabel];

    [@[ firstLabel, secondLabel, thirdLabel ] forEachView:^(UIView *view) {
        UILabel *label = (UILabel *)view;

        label.textColor = [UIColor whiteColor];
        label.font = [UIFont fontWithStyle:FontStyleLight andSize:11];
    }];

    /* line */

    UIView *lineView = [UIView viewWithFrame:CGRectMake(imageSize + labelsWidth + bodyWrapperMargin.x * 2, bodyWrapperMargin.y, 1, bodyWrapperView.frame.size.height - bodyWrapperMargin.y * 2)];

    lineView.backgroundColor = [UIColor whiteColorWithAlpha:0.2];

    [bodyWrapperView addSubview:lineView];

    /* progress */

    CGPoint progressSliderOrigin = CGPointMake(imageSize + labelsWidth + bodyWrapperMargin.x * 3 + 1, bodyWrapperView.frame.size.height / 2 - 15);
    CGSize progressSliderSize = CGSizeMake(bodyWrapperViewSize.width - bodyWrapperMargin.x - progressSliderOrigin.x, 30);

    UISlider *progressSlider = [UISlider viewWithFrame:CGEasyRectMake(progressSliderOrigin, progressSliderSize)];

    progressSlider.value = [self getCategoryProgress];

    [bodyWrapperView addSubview:progressSlider];

    return wrapperView;
}

und ich möchte, dass es ungefähr so ​​aussieht:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = ... // get the view that is already designed in the Interface Builder

    sectionView.headerText = self.categoriesNames[section];
    sectionView.headerImage = [self getCategoryResourceItem:section + 1][@"image"];

    sectionView.firstLabelText = @"first";
    sectionView.secondLabelText = @"second";
    sectionView.thirdLabelText = @"third";

    sectionView.progress = [self getCategoryProgress];

    return wrapperView;
}

Edit 2 : Ich verwende keine Storyboard, nur .xib-Dateien. Ich habe auch keine UITableViewController, nur eine UIViewController, in der ich eine UITableView hinzugefügt habe.

26
Iulian Onofrei

Ich löste es schließlich mit diesem Tutorial , das größtenteils aus folgendem besteht (angepasst an mein Beispiel):

  1. Erstellen Sie eine SectionHeaderView-Klasse, die die Unterklassen UIView umfasst.
  2. Erstellen Sie SectionHeaderView.xib -Datei und setzen Sie die CustomClass von File's Owner auf die SectionHeaderView-Klasse.
  3. Erstellen Sie eine UIView-Eigenschaft in der .m-Datei wie folgt: @property (strong, nonatomic) IBOutlet UIView *viewContent;
  4. Verbinden Sie die .xib-Variable View mit dieser viewContent-Steckdose.
  5. Fügen Sie eine Initialisierungsmethode hinzu, die folgendermaßen aussieht:

    + (instancetype)header {
    
        SectionHeaderView *sectionHeaderView = [[SectionHeaderView alloc] init];
    
        if (sectionHeaderView) { // important part
            sectionHeaderView.viewContent = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:sectionHeaderView options:nil] firstObject];
    
            [sectionHeaderView addSubview:sectionHeaderView.viewContent];
    
            return sectionHeaderView;
        }
    
        return nil;
    }
    

Dann fügte ich eine UILabel in der .xib -Datei hinzu, verband sie mit dem labelCategoryName-Anschluss und implementierte die setCategoryName:-Methode in der SectionHeaderView-Klasse folgendermaßen

- (void)setCategoryName:(NSString *)categoryName {

    self.labelCategoryName.text = categoryName;
}

Ich habe dann die Methode tableView:viewForHeaderInSection: folgendermaßen implementiert:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    SectionHeaderView *sectionHeaderView = [SectionHeaderView header];

    [sectionHeaderView setCategoryName:self.categoriesNames[section]];

    return sectionHeaderView;
}

Und es hat endlich funktioniert. Jeder Abschnitt hat einen eigenen Namen und auch UIImageViews wird korrekt angezeigt.

Ich hoffe, es hilft anderen, die immer und immer wieder über dieselben falschen Lösungen stolpern, im ganzen Web, wie ich es tat.

10
Iulian Onofrei

Storyboard oder XIB

  1. Gleiche Storyboard:

    return tableView.dequeueReusableCell(withIdentifier: "header")
    

     Two Section Headers

  2. Separate XIB (Zusätzlicher Schritt: Sie müssen zuerst Nib registrieren): 

    tableView.register(UINib(nibName: "XIBSectionHeader", bundle:nil),
                       forCellReuseIdentifier: "xibheader")
    

Informationen zum Laden von Storyboard anstelle von XIB finden Sie unter Diese Stack Overflow-Antwort .


Verwenden von UITableViewCell zum Erstellen des Abschnittskopfs in IB

Nutzen Sie die Tatsache, dass ein Abschnittsheader eine reguläre UIView ist und dass UITableViewCell auch eine UIView ist. Ziehen Sie im Interface Builder ein Table View Cell aus der Object Library auf Ihren Table View-Prototyp-Inhalt.

Fügen Sie der neu hinzugefügten Table View Cell einen Identifier hinzu, und passen Sie das Erscheinungsbild an Ihre Anforderungen an. Für dieses Beispiel habe ich header verwendet.

 Edit the cell

Verwenden Sie dequeueReusableCell:withIdentifier, um Ihren Abschnittsheader zu finden, genau wie in einer Tabellensichtzelle. Sie müssen heightForHeaderInSection angeben, das aus Gründen der Klarheit als 44 codiert ist:

//MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
    // This is where you would change section header content
    return tableView.dequeueReusableCell(withIdentifier: "header")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
    return 44
}

Swift 2 & früher:

return tableView.dequeueReusableCellWithIdentifier("header") as? UIView
self.tableView.registerNib(UINib(nibName: "XIBSectionHeader", bundle:nil),
    forCellReuseIdentifier: "xibheader")

► Diese Lösung finden Sie auf GitHub und weitere Details zu Swift Recipes .

76
SwiftArchitect

Lösung ist einfach 

Erstellen Sie ein Xib, erstellen Sie eine Benutzeroberfläche gemäß Ihrer Dokumentation Und rufen Sie dann in viewForHeaderInSection Xib auf

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

        NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];

       HeaderView *headerView = [nibArray objectAtIndex:0];

    return headerView;
} 
1
Bevan

Soweit ich Ihr Problem verstehe, möchten Sie, dass dieselbe UIView für die mehreren Abschnittsheader, die Sie anzeigen möchten, mehrmals dupliziert wird.

Wenn dies mein Problem wäre, würde ich es hier lösen.

ORIGINAL LÖSUNG

1)

In meinem UIViewController, dem die Tabellensicht gehört, würde ich auch eine Ansicht erstellen, die eine Vorlage für den Header ist. Weisen Sie dies einem IBOutlet zu. Dies ist die Ansicht, die Sie mit dem Interface Builder bearbeiten können..

2)

In Ihrer ViewDidLoad- oder (vielleicht besseren) ViewWillAppear-Methode möchten Sie so viele Kopien dieser UIView-Kopfzeilenvorlage erstellen, wie Sie für die Abschnittsheader anzeigen müssen.

Das Erstellen von Kopien von UIViews im Arbeitsspeicher ist nicht trivial, aber auch nicht schwer. Hier ist eine Antwort aus einer verwandten Frage die zeigt, wie es geht.

Fügen Sie die Kopien zu einer NSMutableArray hinzu (wobei der Index jedes Objekts den Abschnitten entspricht ... die Ansicht in Index 0 des Arrays ist das, was Sie für Abschnitt 0, View 1 des Arrays für Abschnitt 1, ec zurückgeben.) .

3)

Sie können IBOutlets nicht für die Elemente dieses Abschnittsheader verwenden (da Ihr Code Auslässe nur mit einer bestimmten Ansicht aus der XIB-Datei verknüpft).

Daher möchten Sie wahrscheinlich für jedes UI-Element in Ihrer Kopfansicht Ansichtseigenschaften verwenden, die Sie für jeden Abschnitt ändern bzw. ändern möchten. Sie können diese Tags über den Interface Builder setzen und dann programmgesteuert in Ihrem Code darauf verweisen.

In Ihrer viewForHeaderInSection-Methode tun Sie Folgendes:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = [self.arrayOfDuplicatedHeaderViews objectAtIndex: section];
    // my title view has a tag of 10
    UILabel *titleToModify = [sectionView viewWithTag: 10]; 
    if(titleToModify)
    {
        titleToModify.text = [NSString stringWithFormat:@"section %d", section];
    }

    return sectionView;
}

Macht Sinn?

VERSCHIEDENE LÖSUNG

1)

Sie würden immer noch ein Array von UIViews (oder "Section View", Unterklassen-UIViews) benötigen, aber Sie könnten jeden mit aufeinanderfolgenden Aufrufen erstellen, um die Ansicht aus der eigenen XIB-Datei zu laden. 

Etwas wie das:

@implementation SectionView

+ (SectionView*) getSectionView
{
  NSArray* array = [[NSBundle mainBundle] loadNibNamed:@"SectionView" owner:nil options:nil];
  return [array objectAtIndex:0]; // assume that SectionView is the only object in the xib
}

@end

(ausführlicher gefunden in der Antwort auf diese verwandte Frage )

2)

Sie können mit {möglicherweise IBOutlets verwenden (aber ich bin nicht zu 100% sicher), aber die Tag-Eigenschaften funktionieren wieder ganz gut. 

0