How to load and pass data from JSON into child view controller?

ghz 8months ago ⋅ 105 views

I want to create a epub book reader app with custom page view controller instead default. I want to load data from JSON file and pass it into child view controller. But I have a problem: my data doesn't showing when I launch the project.

My ParentViewController:

private struct PagesData: Decodable {
    var pagesData: [PageData]
}

private struct PageData: Decodable {
    let textData, textPosition: String
}

class CustomPageViewController: UIViewController {

    private var pagesContent: [ContentViewController] = []
    private var pagesData = [PageData]()
    let contentViewController = ContentViewController()
    
    var pageNumber = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let childViewController = UIStoryboard(name: "Storyboard", bundle: nil).instantiateViewController(withIdentifier: "ContentViewController")
        self.addChildViewController(childViewController)
        self.view.addSubview(childViewController.view)
        childViewController.didMove(toParentViewController: self)
        childViewController.view.frame = self.view.frame
        
        loadData()
    }
            
    func loadData() {
        
        let url = Bundle.main.url(forResource: "pages", withExtension: "json")!
        let data = try! Data(contentsOf: url)
        let result = try! JSONDecoder().decode(PagesData.self, from: data)
        
        for page in result.pagesData {
            let contentVC = ContentViewController()
            contentVC.configure(text: page.textData,
                                image: "image\(pagesContent.count).png",
                                position: page.textPosition)
            pagesContent.append(contentVC)
        }
    }

}

My ChildViewController:

class ContentViewController: UIViewController {
    
    var textPosition = "bottomLeft"
    
    private lazy var textLabel: UILabel = {
        
        let label = UILabel()
        label.textAlignment = .left
        label.numberOfLines = 0
        label.textColor = .white
        label.sizeToFit()
        label.font = UIFont.systemFont(ofSize: 25, weight: .bold)
        label.translatesAutoresizingMaskIntoConstraints = false
        
        return label
        
    }()
    
    private lazy var imageView: UIImageView = {
        
        let image = UIImageView()
        image.contentMode = .scaleToFill
        image.translatesAutoresizingMaskIntoConstraints = false
        image.backgroundColor = .gray
        
        return image
        
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupViews()
        setupConstraints()
    }

    func configure(text: String, image: String, position: String) {
        textLabel.text = text
        imageView.image = UIImage(named: image)
        textPosition = position
    }
    
    private func setupViews() {
        view.addSubview(imageView)
        view.addSubview(textLabel)
    }
        
    private func setupConstraints() {
        
        imageView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 0).isActive = true
        imageView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 0).isActive = true
        imageView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 0).isActive = true
        imageView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 0).isActive = true
        
        switch textPosition {
        case "topCenter":
            
            textLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
                                           constant: 16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16).isActive = true
            
        case "centerCenter":
            
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16).isActive = true
            textLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
            textLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
            
        case "bottomCenter":
            
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16).isActive = true
            
        case "topLeft":
            
            textLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
                                           constant: 16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16 - (view.frame.size.width/2)).isActive = true
            
        case "centerLeft":
            
            textLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
                                           constant: 16).isActive = true
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16 - (view.frame.size.width/2)).isActive = true
            
        case "bottomLeft":
            
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, 
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, 
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16 - (view.frame.size.width/2)).isActive = true
            
        case "topRight":
            
            imageView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
                                           constant: 0).isActive = true
            imageView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, 
                                               constant: 0).isActive = true
            imageView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, 
                                                constant: 0).isActive = true
            
        case "centerRight":
            
            textLabel.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor,
                                           constant: 16).isActive = true
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor, 
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor, 
                                               constant: 16 + (view.frame.size.width/2)).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16).isActive = true
            
        case "bottomRight":
            
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16 + (view.frame.size.width/2)).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor, 
                                                constant: -16).isActive = true
            
        default:
            
            textLabel.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor,
                                              constant: -16).isActive = true
            textLabel.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor,
                                               constant: 16).isActive = true
            textLabel.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor,
                                                constant: -16).isActive = true
        }
    }

}

How to resolve the problem? And what I should fix in this project?

Answers

It seems like the issue might be related to how you're setting up and adding child view controllers in your CustomPageViewController. Here are some suggestions to resolve the problem:

  1. Properly Add Child View Controllers: Make sure you're properly adding the child view controllers to your CustomPageViewController. It seems like you're instantiating a child view controller from a storyboard, but you're not using the contentViewController property you declared. Instead, you're adding the child view controller directly to the parent view controller's view. Modify your code to use the contentViewController property instead:
override func viewDidLoad() {
    super.viewDidLoad()
    
    loadData()
}

func loadData() {
    let url = Bundle.main.url(forResource: "pages", withExtension: "json")!
    let data = try! Data(contentsOf: url)
    let result = try! JSONDecoder().decode(PagesData.self, from: data)
    
    for page in result.pagesData {
        let contentVC = ContentViewController()
        contentVC.configure(text: page.textData,
                            image: "image\(pagesContent.count).png",
                            position: page.textPosition)
        pagesContent.append(contentVC)
    }
    
    // Add the first child view controller to the parent view controller
    if let firstChildVC = pagesContent.first {
        addChild(firstChildVC)
        view.addSubview(firstChildVC.view)
        firstChildVC.didMove(toParent: self)
    }
}
  1. Update Constraints: Ensure that the constraints are set up correctly in your ContentViewController. It seems like you're setting up constraints for both imageView and textLabel, but depending on the textPosition, you may only need to add one of them to the view hierarchy. Double-check the logic in your setupConstraints() method and make sure it aligns with your intended layout.

  2. Ensure JSON Data is Loaded: Verify that your JSON data is properly loaded and decoded. Add some debug print statements to check if the JSON data is loaded correctly and if the decoding process succeeds.

By making these adjustments and ensuring proper setup of child view controllers and constraints, you should be able to resolve the issue and display the data correctly in your app.