bob
bob

Reputation: 111

Swiftui List Row Cells set padding after View appears

I have a standard List only with one Text and on the right side the arrow for navigation. But after the list is loading and appeared at Screen, the list addapt the cells I think they add padding at the left and at the right. But this is not looking good, so it is looking like the list lags!

List {
                       ForEach(0..<book.chapters) { index in
                           NavigationLink(destination: ReadingView(book: self.book, chapter: index)){
                               Text("Kapitel \(index + 1)")
                           }
                       }
        }


           .navigationBarTitle(Text(book.long_name), displayMode: .inline)

enter image description here

Upvotes: 8

Views: 1612

Answers (4)

Sai Durga Mahesh
Sai Durga Mahesh

Reputation: 245

List
{
   ForEach(items)
   {(item) in
      NavigationLink(destination:Destination())
      {
       Text("example")
      }
   }
}.listStyle(InsetListStyle())

The .listStyle(InsetListStyle()) helped me to solve the problem in XCode12.

Upvotes: 0

hustenbonbon
hustenbonbon

Reputation: 96

Edit: iOS 13.6 fixed the issue, my solution following from back then now produces a glitch instead of fixing it!

There is a potential fix for this which produces other complications:

Animations. This weird bug is affected by animations, so just give it an animation like

List {
                   ForEach(0..<book.chapters) { index in
                       NavigationLink(destination: ReadingView(book: self.book, chapter: index)){
                           Text("Kapitel \(index + 1)")
                       }
                   }
    }.animation(.easeInOut(duration: 500))


       .navigationBarTitle(Text(book.long_name), displayMode: .inline)

Will make the bug at least invisible, but be warned that all subviews will inherit that animation, so you manually need to override it again for them. If you are writing a professional app, rather use the TableView fix from Repose's answer.

Strange thing: We found out it happens on iPhone XR and 11 (Simulator and real device) but not on iPhone 11 Pro, so it might only occur on LCD devices???

I hope this helps future visitors of this thread who stumble on this thread like I did while experiencing that bug

Upvotes: 0

Repose
Repose

Reputation: 2207

I've come up with a workaround to fix this issue. One caveat, while it fixes the shift in the table, it introduces a delay in Navigation Bar Title animation, so you are warned.

You can use UIKit's TableViewController in the meantime until Apple fixes SwiftUI's List object.

In summary, you need to create a TableViewController and wrap it in UIViewControllerRepresentable to then inject it into your SwiftUI view. Navigation action is best done through didSelectRowAt delegate method.

Update: seems like the issue has been resolved in the newest XCode 11.4 (however more issues are present in the simulator environment now)

I have a full code here: https://gist.github.com/Rep0se/97d7a97cfd05f42aa597904e6a2cfd3d

//
//  UIKitSwiftUITableView.swift
//  Table Test
//
//  Created on 2020-02-19.
//  Note: While this solution fixes Table shifting bug, it introduces Navigation Bar Title bug for a Large Title. Beware.
//  LBTATools can be downloaded using Swift Package Manager from: https://github.com/bhlvoong/LBTATools
//

import SwiftUI
import LBTATools

struct UIKitSwiftUITableView: View {
    var body: some View {
        NavigationView {
            UIKitSwiftUIContainer()
        }
    }
}

struct Restaurante: Hashable {
    let name: String
    let image: String
}

struct UIKitSwiftUIContainer: View {
    let restaurants = [
        Restaurante(name: "Joe's Original", image: "house"),
        Restaurante(name: "Pheasant Plucker", image: "house.fill"),
        Restaurante(name: "Radius", image: "music.house"),
        Restaurante(name: "The Ship", image: "music.house.fill")
    ]
    var body: some View {
        UIKitTableViewRepresentable(restaurants: restaurants)
            .navigationBarTitle("Select a restaurant") // <- UI bug exests for Navigation Bar Title
            .edgesIgnoringSafeArea(.all)
    }
}

struct UIKitTableViewRepresentable: UIViewControllerRepresentable {
    typealias UIViewControllerType = UIViewController

    let restaurants: [Restaurante]
    init(restaurants: [Restaurante]) {
        self.restaurants = restaurants
    }

    func makeUIViewController(context: UIViewControllerRepresentableContext<UIKitTableViewRepresentable>) -> UIViewController {
        UIKitComboTableViewController(restaurants: restaurants)
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<UIKitTableViewRepresentable>) {

    }
}

class UIKitComboTableViewController: UITableViewController {

    let reuseIdentifier = "reuseIdentifier"
    var restaurants: [Restaurante]
    init(restaurants: [Restaurante]) {
        self.restaurants = restaurants
        super.init(style: .insetGrouped)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(TableCell.self, forCellReuseIdentifier: reuseIdentifier)
    }

    // MARK: - Table view data source
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete implementation, return the number of rows
        return restaurants.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        if let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as? TableCell {
            cell.viewModel.name = restaurants[indexPath.row].name
            cell.viewModel.image = restaurants[indexPath.row].image
            cell.accessoryType = .disclosureIndicator
            return cell
        } else {
            return UITableViewCell()
        }
    }
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let hostingController = UIHostingController(rootView: UIKitSwiftUIContainer())
        show(hostingController, sender: self)
    }
}

class TableCell: UITableViewCell {
    let viewModel = RestaurantViewModel()
    lazy var row = ListRowView(viewModel: viewModel)
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)

        let hostingController = UIHostingController(rootView: row)
        addSubview(hostingController.view)
        hostingController.view.fillSuperview()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

struct ListRowView: View {
    @ObservedObject var viewModel: RestaurantViewModel
    var body: some View {
        HStack{
            Image("Avatar").renderingMode(.original).padding()
            Text(viewModel.name)
                .foregroundColor(.black)
            Spacer()
        }.frame(minHeight: 44)
    }
}

class RestaurantViewModel: ObservableObject {
    @Published var name = ""
    @Published var image = ""
}

struct UIKitSwiftUITableView_Previews: PreviewProvider {
    static var previews: some View {
        UIKitSwiftUITableView()
    }
}

Upvotes: 0

user12132829
user12132829

Reputation:

I have the same issue but only on the simulator. When I’m running the app on any phone, no matter how old, it works perfectly fine as one would expect. You should try that.

Edit: ah now I see the mobile data, you are on your phone. In that case you can file a bug report to Apple and wait and always use newest software.

Upvotes: 1

Related Questions