skip to Main Content

TableViewController:

import UIKit

class NewsTableViewController: UITableViewController {
    var newsItems: [NewsItem] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "NewsCell")
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return newsItems.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "NewsCell", for: indexPath)
        let newsItem = newsItems[indexPath.row]
        cell.textLabel?.text = newsItem.title
        cell.detailTextLabel?.text = formatDate(newsItem.date)
        cell.textLabel?.numberOfLines = 0
        cell.detailTextLabel?.numberOfLines = 0
        return cell
    }

    private func formatDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .short
        formatter.timeStyle = .none
        return formatter.string(from: date)
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let newsItem = newsItems[indexPath.row]
        let alert = UIAlertController(title: newsItem.title, message: newsItem.content, preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}

Update to add NewsListViewModel

import Foundation
import Parse

class NewsListViewModel: ObservableObject {
    @Published var newsItems: [NewsItem] = []

    func fetchNews() {
        let query = PFQuery(className: "News")
        query.order(byDescending: "updatedAt")

        query.findObjectsInBackground { (objects, error) in
            if let error = error {
                print("Error fetching news: (error.localizedDescription)")
            } else if let objects = objects {
                let fetchedNews = objects.compactMap { object -> NewsItem? in
                    guard let title = object["title"] as? String,
                          let date = object.updatedAt,
                          let description = object["description"] as? String else {
                        return nil
                    }
                    return NewsItem(id: UUID(), title: title, date: date, content: description)
                }

                DispatchQueue.main.async {
                    self.newsItems = fetchedNews
                    print("Fetched (fetchedNews.count) news items")
                }
            }
        }
    }
}

I really have no clue what’s going on here. I get objects from Parse, it shows them in a table view controller, and when I tap a row it opens a sheet. The sheet is supposed to display the content of the news item, which is from the key description. For some reason, it jumps straight to the else statement of the if let.

struct NewsAndAlertsView: View {
    @ObservedObject private var viewModel = NewsListViewModel()
    
    @State private var selectedNewsItem: NewsItem? = nil
    @State private var isPresentingModal = false

    var body: some View {
        VStack {
            if viewModel.newsItems.isEmpty {
                Text("Loading news...")
                    .font(.headline)
                    .padding()
                
            } else {
                NewsListView(newsItems: viewModel.newsItems) { newsItem in
                    print("Selected News Item: (newsItem.title)")
                    self.selectedNewsItem = newsItem
                    self.isPresentingModal = true

                }
            }
        }
        
        .navigationBarTitle("News & Alerts", displayMode: .inline)
        .onAppear {
            viewModel.fetchNews()
        }
        .sheet(isPresented: $isPresentingModal) {
            if let newsItem = self.selectedNewsItem {
                VStack {
                    Text(newsItem.title)
                        .font(.largeTitle)
                        .padding()

                    Text(formatDate(newsItem.date))
                        .font(.subheadline)
                        .padding()

                    Text(newsItem.content)
                        .padding()
                    
                    Spacer()
                }
                .padding()
                .onAppear {
                    print("Sheet Title: (newsItem.title)")
                    print("Sheet Date: (formatDate(newsItem.date))")
                    print("Sheet Content: (newsItem.content)")
                }
            } else {
                    Text("No News Item Selected")
                
            }
        }
    }

When I’ve done debug for self.selectedNewsItem I get this:

Optional(ChurchApp.NewsItem(id: 2ECF3231-2DB9-4F28-8B4C-6DC1305D50B2, title: "Test", date: 2024-08-06 02:07:05 +0000, content: "Just a test"))

Again the issue seems to be related to the if let newsItem = self.selectedNewsItem { line of code. But as I’m new to Swift, I’m lost.

2

Answers


  1. This behavior in SwiftUI might not be ideal (in my opinion), but you can work around it using a different API designed for this purpose.

    You can combine your presentation logic with the nil check like this:

    ...
    }
    .sheet(item: $selectedNewsItem) { newsItem in  // 👈 At this point, newsItem is guaranteed to be non-nil
        VStack {
            Text(newsItem.title)
            ...
    

    Note that NewsItem must conform to Identifiable for this to work. If it doesn’t already conform, you can make it conform like this:

    extension NewsItem: Identifiable {
        var id: String { title }  // 👈 Title is just an example; use a unique property as the identifier
    }
    
    Login or Signup to reply.
  2. You can extract the views in the sheet and use a binding variable in that extracted view. Using a binding will update the view when the variable is updated:

        .sheet(isPresented: $isPresentingModal) {
            ExtractedView(selectedNewsItem: selectedNewsItem)
        }
    

    The extracted view code:

    struct Extracted
    
        View: View {
            @Binding var selectedNewsItem: NewsItem?
            var body: some View {
                if let newsItem = self.selectedNewsItem {
                    VStack {
                        Text(newsItem.title)
                            .font(.largeTitle)
                            .padding()
        
                        Text(formatDate(newsItem.date))
                            .font(.subheadline)
                            .padding()
        
                        Text(newsItem.content)
                            .padding()
        
                        Spacer()
                    }
                    .padding()
                    .onAppear {
                        print("Sheet Title: (newsItem.title)")
                        print("Sheet Date: (formatDate(newsItem.date))")
                        print("Sheet Content: (newsItem.content)")
                    }
                } else {
                    Text("No News Item Selected")
                }
            }
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search