2015-07-20 5 views
1

Я работаю над приложением, которое имеет тот же интерфейс, что и в «Instagram». У меня есть кормовая стена, в которой пользователь прокручивает и видит новую картинку, добавленную друзьями, похожую на instagram. Каждый UITableViewcell имеет одно изображение и собственный заголовок заголовка раздела. Я достиг этого, создавая разделы для каждой ячейки. Но я беспокоюсь о memoryleakПользовательский вид UITableView для заголовка раздела (утечки памяти). Каков правильный способ создания пользовательского представления заголовка раздела?

Вот мой код

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 

     let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45)) 
     headerView.tag = 101 

     let imageView :UIImageView = UIImageView(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(22, 5 , 30, 30))) 
     imageView.backgroundColor = UIColor.blueColor() 
     imageView.tag = 101 
     imageView.layer.cornerRadius = 15.0 
     imageView.clipsToBounds = true 
     imageView.layer.borderWidth = 1 
     imageView.layer.borderColor = UIColor.grayColor().CGColor 
     headerView.addSubview(imageView) 

     var label = UILabel(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(50, 5, 320 - 60 , 35))) 
     label.textAlignment = NSTextAlignment.Left 
     label.backgroundColor = UIColor.clearColor() 
     label.textColor = UIColor(red: 1, green: 0.28, blue: 0.27, alpha: 1) 
     label.text = "I'am a test label" 
     label.font = UIFont(name: Constants.FONT_MEDIUM, size: 12) 
     headerView.addSubview(imageView) 
     headerView.backgroundColor = UIColor.clearColor() 

    return headerView 
} 

Btw Я использую Swift. Это так здорово, не так ли?). Позвольте мне подробнее объяснить эту функцию делегата для создания заголовка пользовательского раздела. Каждый раз, когда на экране появляется какая-либо из моей ячейки или секции, этот метод делегата вызывается, даже если я перейду обратно в раздел, чей заголовок уже выделен. Поэтому заголовокView создается несколько раз для одного раздела при прокрутке. Но я знаю, что это неправильный метод. Должно быть что-то, чтобы проверять погоду headerView уже создан или нет. Есть ли способ поставить код проверки, если мои представления существуют в памяти, тогда нет необходимости снова выделять память. Кто-нибудь, пожалуйста, помогите :). Я позабочусь даже о небольшой помощи.

ответ

1

Вам необходимо реализовать кэширование представлений заголовков самостоятельно. Структура не сделает это за вас. Вы могли бы просто создать переменную экземпляра массива для хранения всех выделенных представлений. В методе делегата вы создаете и кешируете или просто возвращаете представление заголовка.

var headerViews = [UIView]() 

func createHeaderView() -> UIView { 
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45)) 

    ... 

    return headerView 
} 

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 
    while section >= headerViews.count { 
     headerViews.append(createHeaderView()) 
    } 

    var headerView = headerViews[section] 

    // Set up the header view (image, text, ...) 

    return headerView 
} 

Обратите внимание, что это, вероятно, не самый эффективный способ сделать это, так как вы будете держать все представления заголовка вокруг, когда они были созданы. Когда вы знаете максимальное количество просмотров заголовков, отображаемых одновременно, вы можете включить какую-то стратегию выключения кеша и повторно использовать существующие заголовки.


Update

Ниже приведен вариант приведенного выше кода, многократно использующего максимум 20 просмотров заголовков. Это будет работать до тех пор, пока заголовки, разделенные на 20 разделов, одновременно не отображаются на экране.

var headerViews = [UIView]() 

func createHeaderView() -> UIView { 
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45)) 

    ... 

    return headerView 
} 

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 
    let maxNumHeaderViews = 20 
    let index = section % maxNumHeaderViews 

    while index >= headerViews.count { 
     headerViews.append(createHeaderView()) 
    } 

    var headerView = headerViews[index] 

    // Set up the header view (image, text, ...) 

    return headerView 
} 

Ключевым моментом может быть то, что в коде, опубликованном в вашем вопросе, отсутствует утечка памяти. Cocoa сохранит представление, которое вы вернете, сохраняется до тех пор, пока оно отображается, и отпустите его, когда он больше не нужен.

+0

Но что, если мне нужны бесконечные ячейки? Поскольку каналы в питающей сети не ограничены. – Ankush

+0

@Ankush Я добавил небольшую вариацию, которая повторно использует не более 20 разных заголовков, чтобы дать вам представление. – hennes

+0

Спасибо, я ценю это :) – Ankush

4

Обычно вам требуется одинаковое использование памяти и шаблон повторного использования ячеек, который Cocoa предоставляет для ячеек таблицы, поэтому используйте UITableViewHeaderFooterView. В viewDidLoad: вызове tableView.registerClass(UITableViewHeaderFooterView.classForCoder(), forHeaderFooterReuseIdentifier:"Header")

Теперь вы можете из очереди этих взглядов по требованию в ваших методах делегата, такие как:

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { 

    let headerView: UITableViewHeaderFooterView 
    if let reusedView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as? UITableViewHeaderFooterView { 
     // Remove the existing contents of the reused view 
     map(reusedView.contentView.subviews) { 
      $0.removeFromSuperview() 
     } 
     headerView = reusedView 
    } else { 
     headerView = UITableViewHeaderFooterView(reuseIdentifier:"Header") 
    } 

    let imageView = ... // Set up image view 
    let label = ... // Set up label 

    headerView.contentView.addSubview(imageView) 
    headerView.contentView.addSubview(label) 

    return headerView 
} 

какао распоряжаются и повторно используете эти представления заголовка, как нужно сейчас.

Эта реализация намного более прямолинейна, если вы используете подкласс UITableViewHeaderFooterView и добавляете свои свойства изображения и метки (тогда вам не нужно будет удалять, а затем добавлять объекты в виде объекта, которые будут заменены, только заменить их по имени). Также, если вы создаете наконечник для своего класса или используете ячейку просмотра таблицы для просмотра через раскадровку, dequeuing никогда не вернет нуль.

Поскольку вы упоминаете, что у вас могут быть «бесконечные ячейки», может быть, вы беспокоитесь о том, чтобы хранить слишком много вашего источника данных в памяти (возможно, у вас есть огромное количество больших изображений для этих изображений). В этом случае вам нужно настроить какой-то кеш. Взгляните на NSCache, если вы доберетесь до этой точки. Нет необходимости реализовывать свой собственный кеш.

По просьбе OP я подробно расскажу о том, как эта система dequeue работает в Cocoa. Вероятно, у вас есть аналогичный код в вашем делекторе tableView:cellForRowAtIndexPath:. Представьте, что ваш пользователь прокручивается вниз через ваш вид таблицы. У вас есть ячейки и заголовки, выходящие за пределы экрана в верхней части табличного вида, и новые, появляющиеся внизу таблицы. Все эти ячейки имеют аналогичный формат. Теперь предположим, что Cocoa не делает ничего особенного (без очереди ячеек). Исчезающие клетки будут освобождены, и появляющиеся ячейки будут созданы с нуля. Вместо этого Cocoa пытается быть эффективным и держит очередь этих ячеек/заголовков под рукой. Он не будет создавать ничего больше, чем нужно отображать на экране в любой момент времени. Когда одна из ячеек выходит за пределы экрана, она помещает ее в очередь, и затем эта ячейка становится доступной для удаления из очереди (выведенной из очереди) позже вами. Когда вы деактивируете ячейку или заголовок (вы запрашиваете ее в соответствии со строкой идентификатора повторного использования), она даст вам точный вид, который вышел на экран (или один из них). Как только вы его получите, настройте его по своему усмотрению. Да, в приведенном выше коде вам необходимо создать UIImageView и UILabel (я объясню, как вы можете и должны делать еще лучше ниже), но основные UIView и любые виды декорирования, которые не нужно менять, будут все еще там (нет вычислительных затрат на повторное использование их). Таким образом, в основном Cocoa помогает вам перерабатывать эти взгляды, делая вещи очень эффективными и безопасными.

Вы заметите, что, когда код выше пытается удалить ячейку, он находится в операторе if let, который проверяет, является ли он нолем. Это важно, потому что, если вы просто загружаете представление в таблицу, у него нет ячеек/представлений в очереди, чтобы дать вам, поэтому, если он равен нулю, вы создаете новый вид с нуля. Когда вы создаете экземпляр, вы даете ему строку идентификатора повторного использования. Если это представление выходит за пределы экрана позже, Cocoa знает, чтобы поместить его в свою специальную очередь, чтобы позже вернуться к вам. Однако, как я заметил выше, если ваше представление загружается из ниба или раскадровки, dequeuing всегда будет возвращать non-nil. Еще одна детальная информация о загрузке с наконечника, вы хотите зарегистрировать идентификатор повторного использования, используя метод registerNib:forHeaderFooterReuseIdentifer:UITableView.

Теперь вы можете увидеть, как Cocoa обрабатывает эти ячейки/просматривать очень эффективно, не требуя, чтобы вы создавали новые представления, когда ваш пользователь прокручивает представление таблицы, вы можете немного разочароваться в моем коде выше, который все еще удаляет все старых представлений и создания новых UIImageView s и UILabel s, и действительно, вы должны быть разочарованы. Рекомендуемый способ обойти это путем подкласса UITableViewHeaderFooterView, или вы действительно можете подклассы UIView. Создайте свойство для imageView и метки. Теперь вместо того, чтобы уничтожить подпункты со старым контентом и затем снова создать эти представления, просто замените их содержимое новым соответствующим контентом (imageView.image = newUIImage и label.text = newString). Верьте или нет, это сэкономит массу вычислений. UIView s - несколько дорогостоящий для создания. Неаккуратная (хотя и потенциально более гибкая) альтернатива подклассу будет заключаться в том, чтобы поместить теги в эти представления, которые вы хотите вставить, используя свойство tag, а затем получить ссылку на эти представления позже, используя headerView.viewWithTag(anInt).

И я ненавижу, чтобы все эти разговоры о заголовках были зря, но я сделаю последнее замечание. Я заметил, что вы говорите, что каждая ячейка имеет свой собственный заголовок. Если это так, вам, вероятно, будет лучше, просто создав одну большую ячейку таблицы (поместите содержимое заголовка выше обычного содержимого ячейки таблицы). Вы можете пропустить использование заголовков все вместе. Однако я не знаю всего, что происходит в вашем приложении, поэтому я просто позволю вам судить об этом.

Удачи вам @ Анкуш! Знание веревок вокруг UITableView - отличное умение iOS, которое, я уверен, вы найдете полезным в будущем!

+0

Спасибо за ваш ответ. Кажется, он работает :). Можете ли вы подробнее рассказать о своем ответе? Я новичок в случае UItableView. Я не знаю, как именно эта вещь дежурит. Означает ли этот приведенный выше код каждый раз, когда раздел будет на экране, тогда код удалит все подпункты и воссоздает их? – Ankush

+0

Счастливые разработки, @ Ankush. Это получилось немного длиннее, поэтому я получил его в своем отредактированном ответе выше. –

+0

Спасибо за это объяснение. Еще один вопрос. Как мне, вероятно, нужно иметь бесконечные ячейки, не будет ли проблем с памятью? Как вы объяснили, какао помещает каждую ячейку в очередь, которая находится вне экрана (это означает, что каждая ячейка будет присутствовать в памяти). Так должен ли я сделать свою логику для создания этого бесконечного tableView? – Ankush