Як зробити так, щоб прокрутка TableView усередині ScrollView вела себе природно


80

Мені потрібно зробити цю програму, яка має дивну конфігурацію.

Як показано на наступному зображенні, основним видом є UIScrollView. Тоді всередині він повинен мати UIPageView, а кожна сторінка PageView повинна мати UITableView.

малюнок

Я все це робив досі. Але моя проблема в тому, що я хочу, щоб прокрутка вела себе природно .

Наступне - це те, що я маю на увазі природно . В даний час, коли я прокручую один з UITableViews, він прокручує перегляд таблиці (а не прокручування). Але я хочу, щоб він прокручував ScrollView, крім випадків, коли прокручуваний перегляд не може прокручуватись, оскільки він потрапив у верхню чи нижню частину (у такому випадку я хотів би, щоб він прокрутив табличний перегляд).

Наприклад, скажімо, мій прокручуваний перегляд зараз прокручується вгору. Потім я кладу пальцем на вигляд таблиці (поточної сторінки, що відображається) і починаю прокручувати вниз . У цьому випадку я хочу, щоб прокручуване прокручувалось (немає перегляду таблиці). Якщо я продовжую прокручувати свій прокручуваний перегляд і він досягає дна, якщо я виймаю палець з дисплея і повертаю його назад над теблевим переглядом і прокручую знову, я хочу, щоб мій перегляд таблиці прокручувався зараз, тому що прокручуваний перегляд досягнув нижньої частини, і це не так здатний продовжувати прокручування.

Ви, хлопці, маєте уявлення про те, як реалізувати цю прокрутку?

Я НАДІЙНО загубився з цим. Будь-яка допомога буде вдячна :(

Дякую!


Що станеться, якщо ваш користувач хоче прокрутити назад у вікні прокрутки? Чи потрібно їм прокручувати вгору в поданні таблиці?
Даніель Чжан,

Ні @DanielZhang. У такому випадку перегляд прокрутки повинен прокручуватися вгору, поки він більше не зможе прокручуватись. Тоді настав час прокрутити таблицю вгору.
Marcela Mukel Balady

Я додав відповідь і зрозумів, що зробив протилежне тому, що ви описуєте щодо прокрутки вгору в поданні таблиці. Мені це здається природним. Чи можете ви описати далі, як поведінка повинна бути іншою?
Даніель Чжан,

Відповіді:


61

Рішення для одночасної обробки подання прокрутки та подання таблиці обертається навколо UIScrollViewDelegate. Отже, нехай ваш контролер перегляду відповідає цьому протоколу:

class ViewController: UIViewController, UIScrollViewDelegate {

Я буду представляти вид прокрутки та подання таблиці як розетки:

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!

Нам також потрібно буде відстежувати висоту вмісту перегляду прокрутки, а також висоту екрана. Пізніше ви зрозумієте чому.

let screenHeight = UIScreen.mainScreen().bounds.height
let scrollViewContentHeight = 1200 as CGFloat

Потрібна невелика конфігурація в viewDidLoad::

override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.contentSize = CGSizeMake(scrollViewContentWidth, scrollViewContentHeight)
    scrollView.delegate = self
    tableView.delegate = self 
    scrollView.bounces = false
    tableView.bounces = false
    tableView.scrollEnabled = false
}

де я вимкнув підстрибування, щоб зробити речі простішими. Ключовими налаштуваннями є представники для перегляду прокрутки та подання таблиці та спочатку вимкнення прокрутки подання таблиці.

Вони необхідні для того, щоб scrollViewDidScroll:метод делегатів міг обробляти нижню частину подання прокрутки та верхню частину подання таблиці. Ось такий метод:

func scrollViewDidScroll(scrollView: UIScrollView) {
    let yOffset = scrollView.contentOffset.y

    if scrollView == self.scrollView {
        if yOffset >= scrollViewContentHeight - screenHeight {
            scrollView.scrollEnabled = false
            tableView.scrollEnabled = true
        }
    }

    if scrollView == self.tableView {
        if yOffset <= 0 {
            self.scrollView.scrollEnabled = true
            self.tableView.scrollEnabled = false
        }
    }
}

Те, що робить метод делегата, виявляє, коли подання прокрутки досягло кінця. Коли це сталося, подання таблиці можна прокрутити. Він також визначає, коли подання таблиці досягає вершини, де перегляд прокрутки знову ввімкнено.

Я створив GIF для демонстрації результатів:

введіть тут опис зображення


7
Привіт, Даніель, дякую за вашу відповідь. Це працює! Хоча користувачеві потрібно прокручувати двічі кожного разу, коли вмикається / вимикається одна прокрутка. Я маю на увазі, що в цей момент прокрутка не переходить з одного сувої на інший. Ви уявляєте, як цього досягти? Ще раз спасибі;)
Marcela Mukel Balady

1
Ласкаво просимо, Марсела. Щоб відповісти на ваше запитання, я не знаю жодного зручного способу перенесення прокрутки з одного виду прокрутки на інший. Там корисно супутня інформація в stackoverflow.com/questions/21554327 / ... . Іноді не варто займатися чимось, що вимагає надмірних налаштувань через пов’язану роботу разом із ризиком того, що вона зламається в майбутніх випусках ОС.
Даніель Чжан,

@DanielZhang для передачі прокрутки просто поділіться .contentOffset між поданнями, які ви хочете синхронізувати, коли викликається метод делегування scrollViewDidScroll.
Ян

@Daniel Zhang Хороша робота та демонстрація, але в моєму випадку виникла проблема, пов’язана з обмеженнями, які я встановив для перегляду, прокрутки та таблиці. Після того, як я це виправив, він працює за 1 секунду.
Раві

@DanielZhang яка висота перегляду таблиці для цього коду?
e.ozmen

28

Модифікована відповідь Даніеля, щоб зробити її більш ефективною та без помилок.

@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var tableHeight: NSLayoutConstraint!

override func viewDidLoad() {
    super.viewDidLoad()
    //Set table height to cover entire view
    //if navigation bar is not translucent, reduce navigation bar height from view height
    tableHeight.constant = self.view.frame.height-64
    self.tableView.isScrollEnabled = false
    //no need to write following if checked in storyboard
    self.scrollView.bounces = false
    self.tableView.bounces = true
}

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 20
}

func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let label = UILabel(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 30))
    label.text = "Section 1"
    label.textAlignment = .center
    label.backgroundColor = .yellow
    return label
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
    cell.textLabel?.text = "Row: \(indexPath.row+1)"
    return cell
}

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    if scrollView == self.scrollView {
        tableView.isScrollEnabled = (self.scrollView.contentOffset.y >= 200)
    }

    if scrollView == self.tableView {
        self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
    }
}

Повний проект можна переглянути тут: https://gitlab.com/vineetks/TableScroll.git


@Vinit Kumar Singh Great це вирішило проблему. Щиро дякую
Vinayak Bhor

@Vineet Kumar Якщо у мене немає навігаційної панелі, то як це зробити? у вашому коді, якщо ми видалимо навігаційну панель, вона прокрутиться лише до рядка 11
Бханутея,

1
64 - це висота навігаційної панелі за замовчуванням. Спробуйте зменшити його до нуля в методі viewDidLoad ().
Vineet Kumar Singh

Це робоче рішення. @Marcela Прийміть цю відповідь
Naveed Ahmad

Робоче рішення, працювало для мене.
Pritesh

4

Після багатьох спроб та помилок саме це мені найбільше вдалося. Рішення має вирішити дві потреби 1) визначити, кому слід використовувати властивість прокрутки; tableView або scrollView? 2) переконайтеся, що tableView не надає повноважень scrollView, поки він не досягне верхньої частини таблиці / вмісту.

Для того, щоб побачити, чи слід використовувати scrollview для прокрутки проти view, я перевірив, чи UIView прямо над моїм view в таблиці. Якщо UIView знаходиться в кадрі, можна впевнено сказати, що scrollView повинен мати повноваження для прокрутки. Якщо UIView не знаходиться в кадрі, це означає, що tableView займає все вікно, і тому він повинен мати повноваження прокручувати.

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    if scrollView.bounds.intersects(UIView.frame) == true {
     //the UIView is within frame, use the UIScrollView's scrolling.   

        if tableView.contentOffset.y == 0 {
            //tableViews content is at the top of the tableView.

        tableView.isUserInteractionEnabled = false
       tableView.resignFirstResponder()
        print("using scrollView scroll")

        } else {

            //UIView is in frame, but the tableView still has more content to scroll before resigning its scrolling over to ScrollView.

            tableView.isUserInteractionEnabled = true
            scrollView.resignFirstResponder()
            print("using tableView scroll")
        }

    } else {

        //UIView is not in frame. Use tableViews scroll.

        tableView.isUserInteractionEnabled = true
       scrollView.resignFirstResponder()
        print("using tableView scroll")

    }

}

сподіваюся, це комусь допоможе!


що має бути UIView.frame?
Andy Obusek,

@AndyObusek будь-який UIView, що знаходиться над вашим видом таблиці.
Felecia Genet,

Не могли б ви завантажити такий проект, оскільки багато людей намагаються побачити, як це працює. Я застосував ваше рішення в демонстраційному проекті Vineet нижче (оскільки це було легко доступно для тестування), але перегляд таблиці всередині заблоковано, а прокрутка не передана.
Паван

2
Ваше рішення не переносить сувій одночасно. Потрібно двічі торкнутися та прокрутити, щоб перейти від перегляду прокрутки до прокрутки у вигляді таблиці.
Паван

3

Я теж боровся з цією проблемою. Є дуже просте рішення.

У конструкторі інтерфейсів:

  1. створити простий ViewController
  2. додайте простий View, це буде наш заголовок, і обмежте його суперпереглядом
    • це червоний вигляд на прикладі нижче
    • Я додав 12 пікселів зверху, ліворуч та праворуч і встановив фіксовану висоту 128 пікселів
  3. вбудувати PageViewController, переконавшись, що він обмежений суперпреглядом, а не заголовком

ib

Тепер приходить найцікавіша частина: для кожної доданої сторінки переконайтеся, що її tableView має зміщення зверху. Це воно. Ви можете зробити, якщо з цим кодом, наприклад (якщо ви використовуєте UITableViewController як сторінку):

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let tables = viewControllers.compactMap { $0 as? UITableViewController }
    tables.forEach {
        $0.tableView.contentInset = UIEdgeInsets(top: headerView.bounds.height, left: 0, bottom: 0, right: 0)
        $0.tableView.contentOffset = CGPoint(x: 0, y: -headerView.bounds.height)
    }
}

Ніякої брудної прокрутки всередині прокрутки всередині подання таблиці, жодного перекручування з делегатами, ніяких дубльованих прокруток, абсолютно природна поведінка. Якщо ви не бачите заголовок, можливо, це пов’язано з кольором тла tableView. Ви повинні встановити його очищення, щоб заголовок був видно з-під tableView.


Взаємодія користувача з поданням заголовка неможлива з цим рішенням, чи не так?
Кнут Вален

Я не міг зробити цю роботу. Заголовки розділів для таблиці невидимі над contentInset / offset, а кнопки в заголовку не можна натиснути. Прокрутка є плавною і відчуває себе добре.
Кнут Вален

3

Якщо ви зіткнулися з проблемою вкладеної проблеми прокрутки, ось найпростіший спосіб вирішити її.

  1. перейдіть на екран дизайну
  2. виберіть свій вид прокрутки, а потім вимкніть відскакування при прокрутці
  3. якщо у вашому поданні використовується перегляд таблиці у вигляді прокрутки, тоді також вимкніть відскакування при прокрутці вигляду таблиці
  4. запустити і перевірити, чи вирішено

перевірити, як вимкнути відмов при прокрутці подання прокрутки

перевірити, як вимкнути відмов при прокрутці подання у вигляді таблиці


2

Жодна з відповідей тут не працювала для мене ідеально. Кожен з них мав нюансовану проблему (потрібно повторно проводити пальцем, коли один прокручувальний перегляд потрапляє внизу, або індикатор прокрутки виглядає неправильно тощо), тож я вирішив, що кину іншу відповідь.

Оле Бегеманн чудово писав, що робить це саме https://oleb.net/blog/2014/05/scrollviews-inside-scrollviews/

Незважаючи на те, що це стара публікація, концепції все ще застосовуються до поточних API. Крім того, існує підтримана (сумісна з Xcode 9) ціль-C реалізації його підходу https://github.com/eyeem/OLEContainerScrollView


1

Думаю, є два варіанти.

Оскільки ви знаєте розмір подання прокрутки та основного перегляду, ви не можете визначити, чи подання прокрутки потрапило внизу чи ні.

if (scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)) {
    // reach bottom
}

Отже, коли він вдарився; ви в основному встановили

[contentScrollView setScrollEnabled:NO];

і навпаки для вашого tableView.

Інша річ, яка, на мій погляд, є більш точною, - це додати Жест до Ваших поглядів.

UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:@selector(respondToTapGesture:)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[self.view addGestureRecognizer:tapRecognizer];

// Do any additional setup after loading the view, typically from a nib

Отже, коли ви додаєте Жест, ви можете просто керувати активним видом, змінивши setScrollEnabledв respondToTapGesture.


1

Я знайшов чудову бібліотеку MXParallaxHeader

У Storyboard просто встановіть UIScrollViewклас, щоб MXScrollViewтоді відбувалась магія.

Я використовував цей клас для обробки мого, UIScrollViewколи я вбудовував UIPageViewControllerподання контейнера. навіть ви можете вставити вигляд заголовка паралаксу для отримання більш детальної інформації.

Крім того, ця бібліотека надає CocoapodsіCarthage

Я долучив зображення, яке внизу представляє UIViewІєрархію. Ієрархія MXScrollView


0

Я спробував рішення, позначене як правильна відповідь, але воно не працювало належним чином. Користувачеві потрібно двічі клацнути на перегляді таблиці для прокрутки, і після цього я не зміг прокрутити весь екран знову. Отже, я щойно застосував наступний код у viewDidLoad ():

tableView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(tableViewSwiped)))
scrollView.addGestureRecognizer(UISwipeGestureRecognizer(target: self, action: #selector(scrollViewSwiped)))

І код нижче - це здійснення дій:

func tableViewSwiped(){
    scrollView.isScrollEnabled = false
    tableView.isScrollEnabled = true
}

func scrollViewSwiped(){
    scrollView.isScrollEnabled = true
    tableView.isScrollEnabled = false
}

0

Можливо, грубою формою, але працює чудово: до речі, я використовую автоматичну розкладку.

для tableView (або collectionView або що завгодно), встановіть довільну висоту в розкадровці та зробіть вихід для класу. Де це доречно, (viewDidLoad () або ...) встановіть висоту tableView досить великою, щоб tableView не потрібно було прокручувати. (потрібно заздалегідь знати кількість рядків) Тоді лише зовнішній scrollView буде красиво прокручуватись.


це не працює для переглядів таблиць з різною висотою комірок
Мохаммад Реза Коохкан,

0

Один простий прийом, якщо ви хочете цього досягти, - це замінити батьківський прокручуваний перегляд звичайним контейнерним видом. Додавши жест панорамування у поданні контейнера, ви можете грати з верхнім обмеженням першого подання, щоб призначити негативні значення. Ви можете продовжувати перевірку походження сторінки Перегляду сторінки, якщо вона досягається вгорі, Ви можете почати призначати це значення для зміщення вмісту дочірнього подання pageView. Поки користувач не досягне подання таблиці в стані найбільш перегляду у перегляді контейнера, ви можете залишати прокрутку сторінки tableView вимкненою і дозволити прокрутку вручну, встановивши зміщення вмісту. Отже, спочатку висота перегляду сторінки буде згорнута (або скажімо поза екраном) або менше внизу. Пізніше при прокрутці вниз він розшириться, щоб зайняти більше місця.

Жест автоматично перестане відповідати, якщо поза рамками буде сказано на панелі навігації або в іншому вигляді поза переглядом контейнера.

Жести - це ключ до інтерактивних переходів користувачів, що використовуються у багатьох додатках. Ви можете імітувати прокрутку протягом певного часу за допомогою нього.


0

У моєму випадку я використовую обмеження для висоти так:

self.heightTableViewConstraint.constant = self.tableView.contentSize.height
self.scrollView.contentInset.bottom = self.tableView.contentSize.height

0

SWIFT 5

У мене виникли проблеми з використанням відповіді Vineet, коли я не міг гарантувати зміщення вмісту scrollView (Y) через різні розміри екрану. Щоб вирішити цю проблему, я змінив першу подію тригера, коли прокрутка tableView вмикається.

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.bounds.contains(button.frame) {
            tableView.isScrollEnabled = true
        }

        if scrollView == tableView {
            self.tableView.isScrollEnabled = (tableView.contentOffset.y > 0)
        }
    }

ScrollView.bounds.contains перевірить, чи є кадр даного елемента ПОВНІМ у видимому вмісті scrollView. Я встановив це для кнопки, яка є у мене під tableView. Натомість ви можете встановити для цього фрейм tableVIew, якщо вашою єдиною умовою є те, що ваш tableView повністю видно.

Я залишив оригінальну реалізацію, коли вимкнути прокрутку tableView, і вона працює дуже добре.


-1
CGFloat tableHeight = 0.0f;

YourArray =[response valueForKey:@"result"];

tableHeight = 0.0f;
for (int i = 0; i < [YourArray count]; i ++) {
    tableHeight += [self tableView:self.aTableviewDoc heightForRowAtIndexPath:[NSIndexPath indexPathForRow:i inSection:0]];
}

self.aTableviewDoc.frame = CGRectMake(self.aTableviewDoc.frame.origin.x, self.aTableviewDoc.frame.origin.y, self.aTableviewDoc.frame.size.width, tableHeight);

Привіт. Питання позначено тегом "швидкий". Будь ласка, напишіть свою відповідь за тегами. Дякую! :)
Ерік Ая

1
Ви також повинні додати пояснення до свого коду. Як цей фрагмент вирішує проблему OP?
Luca Angeletti
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.