Thomas Valenzuela
Thomas Valenzuela

Reputation: 101

1 HomeViewController.actnShare() Testflight crash

I had an app crash from a test flight user. I cannot replicate the issue and was having trouble narrowing it down. Any help or suggestions is appreciated! Here is where the issue is according to xcode: HomeViewController.actnShare()

    @objc func actnShare(){
        let asset = self.allImages[kolodaView.currentCardIndex]
        let image : UIImage = convertImageFromAsset(asset: asset)!
        
        let activityViewController : UIActivityViewController = UIActivityViewController(
            activityItems: [image], applicationActivities: nil)

My HomeViewController code:

import UIKit
import Photos
import PhotosUI
import Koloda
import SimpleImageViewer
import AssetsPickerViewController
import MediaBrowser
import SVProgressHUD

enum SortOrder{
    case Random
    case Ascending
    case Descending
}

class HomeViewController: UIViewController {

    @IBOutlet weak var mainKoladaView: UIView!{
        didSet{
            mainKoladaView.layer.cornerRadius = 10.0
        }
    }
    @IBOutlet weak var mainCardView: UIView!{
        didSet{
            mainCardView.layer.cornerRadius = 10.0
        }
    }
    @IBOutlet weak var submainCardView: UIView!{
        didSet{
            submainCardView.layer.cornerRadius = 20.0
        }
    }
    @IBOutlet weak var imageDateLabel: UILabel!
    @IBOutlet weak var imageCountLabel: UILabel!
    @IBOutlet weak var kolodaView: KolodaView!{
        didSet{
            kolodaView.layer.cornerRadius = 10.0
        }
    }
    @IBOutlet weak var imageCountView: UIView!{
        didSet{
            imageCountView.layer.cornerRadius = imageCountView.frame.height / 2
        }
    }
    @IBOutlet weak var sortOrderLabel: UILabel!
    @IBOutlet weak var sortView: UIView!{
        didSet{
            sortView.layer.cornerRadius = sortView.frame.height / 2
        }
    }
    @IBOutlet weak var dateView: UIView!{
        didSet{
            dateView.layer.cornerRadius = dateView.frame.height / 2
        }
    }
    @IBOutlet weak var lblCount: UILabel!
    @IBOutlet weak var sortButton: UIButton!
    
    var allImagesModels = [PhotoModel]()
    var allImages = [PHAsset]()
    var assets = [PHAsset]()
    var sortOrder : SortOrder = .Random
    
    var mediaArray = [Media]()
    var selectionMedia = [PHAsset]()

    var selections = [Bool]()

    var maximumCountToDelete = 20//100
    var imageCount = 1
    var maximumCountToKeep = 20//100

    var tempDeleteCount = 0
    var tempKeepCount = 0

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        getAllImages()
        selectionMedia = ImageManager.shared.deleteAssetList
        self.setUpCount()
        kolodaView.dataSource = self
        kolodaView.delegate = self
        
        tempDeleteCount = AppManager.shared.userClick
        tempKeepCount = AppManager.shared.keepCount

        
        self.modalTransitionStyle = UIModalTransitionStyle.flipHorizontal
        
        
        
    }
    
    func showUpgradeScreen(assetVC : AssetsPickerViewController?){
        let upgradeVc = self.storyboard?.instantiateViewController(withIdentifier: "UpgradeViewController") as! UpgradeViewController
        
        upgradeVc.modalPresentationStyle = .overCurrentContext
        upgradeVc.modalTransitionStyle = .crossDissolve
        
        upgradeVc.popoverPresentationController?.sourceRect = CGRect(x: 150, y: 150, width: 0, height: 0)
        
        DispatchQueue.main.async {
            if assetVC != nil{
                assetVC?.present(upgradeVc, animated: true, completion: nil)
            }else{
                self.present(upgradeVc, animated: true, completion: nil)
            }
        }
       
    }
    
    func setUpCount(){
        self.lblCount.adjustsFontSizeToFitWidth = true
        
        if selectionMedia.count > 0 {
            self.lblCount.text = "\(selectionMedia.count)"
        }else{
            self.lblCount.isHidden = true
        }
        
        NotificationCenter.default.addObserver(self, selector: #selector(actnCountChange), name: NSNotification.Name(DELETE_PHOTOS_CHANGES), object: nil)
    }
    
    @objc func actnCountChange(){
        if ImageManager.shared.deleteAssetList.count > 0 {
            DispatchQueue.main.async {
                self.lblCount.isHidden = false
                self.lblCount.text = "\(ImageManager.shared.deleteAssetList.count)"
            }
        }else{
            self.lblCount.isHidden = true
        }
    }
    
    func getAllImages(){
        SVProgressHUD.show()
        PHPhotoLibrary.requestAuthorization { status in
            
            switch status {
            case .authorized:
                let fetchOptions = PHFetchOptions()
                fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
                let allPhotos = PHAsset.fetchAssets(with: .image, options: fetchOptions)
                print("Found \(allPhotos.count) assets")
                print("all Photos \(allPhotos)")
                self.addImagetoArray(photos: allPhotos)
                SVProgressHUD.dismiss()
                
            case .denied, .restricted:
                print("Not allowed")
                SVProgressHUD.dismiss()

            case .notDetermined:
                // Should not see this when requesting
                print("Not determined yet")
                SVProgressHUD.dismiss()

         

            case .limited:
                <#code#>
            @unknown default:
                fatalError()

            }
        }
    }
    
    
    func convertImageFromAsset(asset: PHAsset) -> UIImage? {
        
        var img: UIImage?
        let manager = PHImageManager.default()
        let options = PHImageRequestOptions()
        options.version = .original
        options.isSynchronous = true
        manager.requestImageData(for: asset, options: options) { data, _, _, _ in
            
            if let data = data {
                img = UIImage(data: data)
            }
        }
        return img
    }
    
    func addImagetoArray(photos : PHFetchResult<PHAsset>){
        self.allImages.removeAll()
        
        for i in 0...photos.count - 1 {
            let photoAsset = photos.object(at: i)

            self.allImages.append(photoAsset)
        }
        
        self.allImages =  allImages.shuffled()
        
        DispatchQueue.main.async {
            self.imageCountLabel.text = "1/\(self.allImages.count)"
            self.imageDateLabel.text = self.allImages.first?.creationDate?.string(withFormat: "MMM d, yyyy")
            self.kolodaView.reloadData()
        }
    }
    
    //MARK: Actions
    @IBAction func sortAction(_ sender: UIButton) {
        
        let sheet = UIAlertController.init(title: "", message: "Please sort select options", preferredStyle: UIAlertController.Style.actionSheet)
        sheet.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.cancel, handler: { _ in
            //Cancel Action
        }))
        sheet.addAction(UIAlertAction(title: "Random",
                                      style: UIAlertAction.Style.default,
                                      handler: {(_: UIAlertAction!) in
            self.sortOrder = .Random
            self.sortOrderLabel.text = "Random"
            self.allImages = self.allImages.shuffled()
            DispatchQueue.main.async {
                self.imageDateLabel.text = self.allImages.first?.creationDate?.string(withFormat: "MMM d, yyyy")
                self.kolodaView.reloadData()
            }
        }))
        sheet.addAction(UIAlertAction(title: "Newest First",
                                      style: UIAlertAction.Style.default,
                                      handler: {(_: UIAlertAction!) in
            self.sortOrder = .Ascending
            self.sortOrderLabel.text = "Newest First"
            self.allImages = self.allImages.sorted(by: { $0.creationDate?.compare($1.creationDate ?? Date()) == .orderedDescending })
            DispatchQueue.main.async {
                self.imageDateLabel.text = self.allImages.first?.creationDate?.string(withFormat: "MMM d, yyyy")
                self.kolodaView.reloadData()
            }
        }))
        sheet.addAction(UIAlertAction(title: "Oldest First",
                                      style: UIAlertAction.Style.default,
                                      handler: {(_: UIAlertAction!) in
            self.sortOrder = .Descending
            self.sortOrderLabel.text = "Oldest First"
            self.allImages =  self.allImages.sorted(by: { $0.creationDate?.compare($1.creationDate ?? Date()) == .orderedAscending})
            DispatchQueue.main.async {
                self.imageDateLabel.text = self.allImages.first?.creationDate?.string(withFormat: "MMM d, yyyy")
                self.kolodaView.reloadData()
            }
        }))
        self.showSheet(sheet: sheet, sender: UIButton(), owner: self)
        
        
//        if sortOrder == .Random{
//            self.sortOrder = .Descending
//            self.sortButton.setTitle("Sort by: Newest First", for: .normal)
//            self.allImages =  allImages.shuffled()
//        }else if sortOrder == .Descending{
//            self.sortOrder = .Ascending
//            self.sortButton.setTitle("Sort by: Oldest First", for: .normal)
//            self.allImages =  allImages.sorted(by: { $0.creationDate?.compare($1.creationDate ?? Date()) == .orderedDescending })
//        }else{
//            self.sortOrder = .Random
//            self.sortButton.setTitle("Sort by: Random", for: .normal)
//            self.allImages =  allImages.sorted(by: { $0.creationDate?.compare($1.creationDate ?? Date()) == .orderedAscending })
//        }
//        DispatchQueue.main.async {
//            self.imageDateLabel.text = self.allImages.first?.creationDate?.string(withFormat: "MMM d, yyyy")
//            self.kolodaView.reloadData()
//        }
    }
    
    func showSheet(sheet: UIAlertController, sender: UIView, owner: UIViewController) {
        
        switch UIDevice.current.userInterfaceIdiom {
        case .phone:
            owner.present(sheet, animated: true, completion: nil)
        case .pad:
            let popOver = sheet.popoverPresentationController
            
            popOver?.sourceRect = CGRect(x: UIScreen.main.bounds.width / 2, y: UIScreen.main.bounds.height / 2, width: 0, height: 0)
            popOver?.sourceView = sender
            popOver?.permittedArrowDirections = UIPopoverArrowDirection(rawValue: 0)
            
            owner.present(sheet, animated: true, completion: nil)
        case .unspecified:
            owner.present(sheet, animated: true, completion: nil)
        default:
            print("")
        }
        
    }
    
    @IBAction func actnReload(_ sender: Any) {
        kolodaView?.revertAction()
//        showUpgradeScreen()
    }
    
    
    @IBAction func actnRemove(_ sender: Any) {
        DispatchQueue.main.async {
            self.kolodaView?.swipe(.left)
        }
    }
    
    @IBAction func actnLike(_ sender: Any) {
        DispatchQueue.main.async {
            self.kolodaView?.swipe(.right)
        }
    }
    
    
    @IBAction func actnGallery(_ sender: Any) {
        
//        DispatchQueue.main.async {
//            self.kolodaView?.swipe(.right)
//        }
        
        let vc = storyboard?.instantiateViewController(withIdentifier: "AlbumVC") as! AlbumViewController
        vc.albumDelegate = self
        self.navigationController?.pushViewController(vc, animated: true)
//
//        let options = PHFetchOptions()
//        options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
//        options.sortDescriptors = [NSSortDescriptor(key: "duration", ascending: true)]
//
//
//
//        let pickerConfig = AssetsPickerConfig()
//        pickerConfig.selectedAssets = ImageManager.shared.deleteAssetList
//        pickerConfig.assetFetchOptions = [
//            .smartAlbum: options,
//            .album: options
//        ]
//
//        let picker = AssetsPickerViewController()
//        picker.pickerConfig = pickerConfig
//        picker.pickerDelegate = self
//        present(picker, animated: true, completion: nil)
        
    }
    
    @IBAction func actnTrash(_ sender: Any) {
        
        if ImageManager.shared.deleteAssetList.count == 0 {
            return
        }
        mediaArray = ImageManager.shared.getDeleteMediaList()
    
        
        let browser = MediaBrowser(delegate: self)
        browser.displayActionButton = true
        browser.displayMediaNavigationArrows = true
        browser.displaySelectionButtons = true
        browser.alwaysShowControls = true
        browser.zoomPhotosToFill = true
        browser.enableGrid = false
        browser.startOnGrid = true
        browser.enableSwipeToDismiss = true
        browser.autoPlayOnAppear = false
        
        
        selections.removeAll()
        
        for index in 0..<mediaArray.count {
            
            selections.append(true)
            selectionMedia.append(mediaArray[index].asset!)
        }

        self.navigationController?.pushViewController(browser, animated: true)

        
    }
    
    @IBAction func actnSettings(_ sender: Any) {
        let setting = self.storyboard?.instantiateViewController(withIdentifier: "SettingViewController") as! SettingViewController
        self.navigationController?.pushViewController(setting, animated: true)
    }
    
    
    
    @objc func actnShare(){
        let asset = self.allImages[kolodaView.currentCardIndex]
        let image : UIImage = convertImageFromAsset(asset: asset)!
        
        let activityViewController : UIActivityViewController = UIActivityViewController(
            activityItems: [image], applicationActivities: nil)
        
        
        
        activityViewController.popoverPresentationController?.sourceRect = CGRect(x: 150, y: 150, width: 0, height: 0)
        
        // Anything you want to exclude
        activityViewController.excludedActivityTypes = [
            UIActivity.ActivityType.postToWeibo,
            UIActivity.ActivityType.print,
            UIActivity.ActivityType.assignToContact,
            UIActivity.ActivityType.saveToCameraRoll,
            UIActivity.ActivityType.addToReadingList,
            UIActivity.ActivityType.postToFlickr,
            UIActivity.ActivityType.postToVimeo,
            UIActivity.ActivityType.postToTencentWeibo
        ]
        
        self.present(activityViewController, animated: true, completion: nil)
    
    }
    
    @IBAction func actnOpenGallery(_ sender: Any) {
        let options = PHFetchOptions()
        options.predicate = NSPredicate(format: "mediaType = %d", PHAssetMediaType.image.rawValue)
        options.sortDescriptors = [NSSortDescriptor(key: "duration", ascending: true)]
        
        
        
        let pickerConfig = AssetsPickerConfig()
        pickerConfig.selectedAssets = ImageManager.shared.deleteAssetList
        pickerConfig.assetFetchOptions = [
            .smartAlbum: options,
            .album: options
        ]
        
        let picker = AssetsPickerViewController()
        picker.pickerConfig = pickerConfig
        picker.pickerDelegate = self
        present(picker, animated: true, completion: nil)
    }
    
    
}

// MARK: KolodaViewDelegate

extension HomeViewController: KolodaViewDelegate {
    
    func kolodaDidRunOutOfCards(_ koloda: KolodaView) {
//        let position = kolodaView.currentCardIndex
//        for i in 1...4 {
//            dataSource.append(UIImage(named: "Card_like_\(i)")!)
//        }
//        kolodaView.insertCardAtIndexRange(position..<position + 4, animated: true)
    }
    
    func koloda(_ koloda: KolodaView, didSelectCardAt index: Int) {
        let photoModel = allImages[index]
        
        let configuration = ImageViewerConfiguration { config in
            config.image = convertImageFromAsset(asset: photoModel)

        }

        let imageViewerController = ImageViewerController(configuration: configuration)

        self.present(imageViewerController, animated: true, completion: nil)
    }
    
    func koloda(_ koloda: KolodaView, didSwipeCardAt index: Int, in direction: SwipeResultDirection){
        self.selectionMedia = ImageManager.shared.deleteAssetList
        if self.imageCount != self.allImages.count{
            self.imageCount += 1
            self.imageCountLabel.text = "\(self.imageCount)/\(self.allImages.count)"
            if self.allImages.count > index + 1 {
                self.imageDateLabel.text = self.allImages[index + 1].creationDate?.string(withFormat: "MMM d, yyyy")
            }
        }

        if direction == .left{
            
            if tempDeleteCount > maximumCountToDelete && !AppManager.shared.isUpgraded {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    self.kolodaView?.revertAction()
                }
                showUpgradeScreen(assetVC: nil)
                return
            }
            let photoModel = self.allImages[index]
            self.imageCountLabel.text = "\(self.imageCount)/\(self.allImages.count)"
//            AppManager.shared.userClick += 1
            tempDeleteCount += 1
            let filter = self.selectionMedia.filter({$0.localIdentifier == photoModel.localIdentifier})
            if filter.count == 0{
                ImageManager.shared.deleteAssetList.append(photoModel)
            }
            
        }else if direction == .right{
            let photoModel = self.allImages[index]
            if tempKeepCount > maximumCountToKeep && !AppManager.shared.isUpgraded {

                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    self.kolodaView?.revertAction()
                }
                showUpgradeScreen(assetVC: nil)
                return

            }
            tempKeepCount += 1

            let filter = self.selectionMedia.filter({$0.localIdentifier == photoModel.localIdentifier})
            if filter.count > 0,let objectIndex = self.selectionMedia.firstIndex(of: filter[0]){
                ImageManager.shared.deleteAssetList.remove(at: objectIndex)
                tempDeleteCount -= 1
            }
            
        }
        
//        else if direction == .topLeft || direction == .topRight{
//            
//            let photoModel = self.allImages[index]
//            
//            if AppManager.shared.favCount >= maximumCountToFav && !AppManager.shared.isUpgraded {
//                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
//                    self.kolodaView?.revertAction()
//                }
//                showUpgradeScreen(assetVC: nil)
//                return
//            }
//            AppManager.shared.favCount += 1
//            PHPhotoLibrary.shared().performChanges({
//                    let request = PHAssetChangeRequest(for: photoModel)
//                    request.isFavorite = true
//            }) { (is_success, error) in
//               print("Success")
//            }
//
//            
//        }
        
    }
    func koloda(_ koloda: KolodaView, allowedDirectionsForIndex index: Int) -> [SwipeResultDirection]{
        return [.right, .left]
    }
    
    func kolodaSwipeThresholdRatioMargin(_ koloda: KolodaView) -> CGFloat? {
        return 0.5
    }
}

// MARK: KolodaViewDataSource

extension HomeViewController: KolodaViewDataSource {
    
    func kolodaNumberOfCards(_ koloda: KolodaView) -> Int {
        return allImages.count
    }
    
    func kolodaSpeedThatCardShouldDrag(_ koloda: KolodaView) -> DragSpeed {
        return .fast
    }
    
    func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView {
//        let photoModel = allImagesModels[index]
//        let imageView = UIImageView(image: photoModel.image)
//        imageView.contentMode = .scaleAspectFill
//        imageView.clipsToBounds = true
//        return imageView
        
        let shareBtn = UIButton(frame: CGRect(x: kolodaView.frame.width - 70, y: 10, width: 50, height: 50))
        shareBtn.setBackgroundImage(UIImage(named: "Upload"), for: .normal)
        shareBtn.addTarget(self, action: #selector(actnShare), for: .touchUpInside)
        
        let photo = allImages[index]
        let imageView = UIImageView(image: convertImageFromAsset(asset: photo) ?? UIImage())
        imageView.contentMode = .scaleAspectFill
        imageView.clipsToBounds = true
        imageView.isUserInteractionEnabled = true
        imageView.addSubview(shareBtn)
        
        return imageView

    }
    
    func koloda(_ koloda: KolodaView, viewForCardOverlayAt index: Int) -> OverlayView? {
        return Bundle.main.loadNibNamed("OverlayView", owner: self, options: nil)?[0] as? OverlayView
    }
   
}

extension HomeViewController: AssetsPickerViewControllerDelegate {
    
    func assetsPickerCannotAccessPhotoLibrary(controller: AssetsPickerViewController) {
        logw("Need permission to access photo library.")
    }
    
    func assetsPickerDidCancel(controller: AssetsPickerViewController) {
        logi("Cancelled.")
    }
    
    func assetsPicker(controller: AssetsPickerViewController, selected assets: [PHAsset]) {
        self.assets = assets
        
        ImageManager.shared.deleteAssetList = assets
        
//        self.assets.forEach { (photo) in
//
//
//            let filter = selectionMedia.filter({$0.localIdentifier == photo.localIdentifier})
//            if filter.count == 0{
//                ImageManager.shared.deleteAssetList.append(photo)
//            }
//        }
        
       
        
//        ImageManager.shared.deleteAssetList.append(contentsOf: self.assets)
        
        
    }
    
    func assetsPicker(controller: AssetsPickerViewController, shouldSelect asset: PHAsset, at indexPath: IndexPath) -> Bool {
        logi("shouldSelect: \(indexPath.row)")
        
        // can limit selection count
        
        let totalCount = controller.selectedAssets.count + AppManager.shared.userClick
        
        if totalCount >= maximumCountToDelete && !AppManager.shared.isUpgraded {
            showUpgradeScreen(assetVC: controller)
            return false
        }
//        if controller.selectedAssets.count > 3 {
//            // do your job here
//        }
        tempDeleteCount += 1
        
        return true
    }
    
    func assetsPicker(controller: AssetsPickerViewController, didSelect asset: PHAsset, at indexPath: IndexPath) {
        logi("didSelect: \(indexPath.row)")
    }
    
    func assetsPicker(controller: AssetsPickerViewController, shouldDeselect asset: PHAsset, at indexPath: IndexPath) -> Bool {
        logi("shouldDeselect: \(indexPath.row)")
        tempDeleteCount -= 1
        return true
    }
    
    func assetsPicker(controller: AssetsPickerViewController, didDeselect asset: PHAsset, at indexPath: IndexPath) {
        logi("didDeselect: \(indexPath.row)")
    }
    
    func assetsPicker(controller: AssetsPickerViewController, didDismissByCancelling byCancel: Bool) {
        logi("dismiss completed - byCancel: \(byCancel)")
    }

}



//MARK: MediaBrowserDelegate
extension HomeViewController: MediaBrowserDelegate {
    func thumbnail(for mediaBrowser: MediaBrowser, at index: Int) -> Media {
        if index < mediaArray.count {
            return mediaArray[index]
        }
        return Media.init(image: UIImage(named: "SplashScreen")!, caption: "No image selected")
    }
    
    func media(for mediaBrowser: MediaBrowser, at index: Int) -> Media {
        if index < mediaArray.count {
            return mediaArray[index]
        }
        return Media.init(image: UIImage(named: "SplashScreen")!, caption: "No image selected")
    }
    
    func numberOfMedia(in mediaBrowser: MediaBrowser) -> Int {
        return mediaArray.count
    }
    
    func isMediaSelected(at index: Int, in mediaBrowser: MediaBrowser) -> Bool {
        return selections[index]
        
    }
    
    func didDisplayMedia(at index: Int, in mediaBrowser: MediaBrowser) {
        print("Did start viewing photo at index \(index)")
        
    }
    
    func mediaDid(selected: Bool, at index: Int, in mediaBrowser: MediaBrowser) {
        selections[index] = selected
        
        if selected {
            let media = mediaArray[index]
            
            let filter = selectionMedia.filter({$0.localIdentifier == media.asset?.localIdentifier})
            
            if filter.count == 0{
                selectionMedia.append(mediaArray[index].asset!)
            }
        }else{
            let media = mediaArray[index]
            
            if selectionMedia.contains(media.asset!), let indexItem = selectionMedia.firstIndex(of: media.asset!){
                selectionMedia.remove(at: indexItem)
            }
        }
    }
    
  
    
    func actionButtonPressed(at photoIndex: Int, in mediaBrowser: MediaBrowser, sender: Any? = nil) {
        print("delete")
        
//        let assetList = ImageManager.assetFrom(mediaList: selectionMedia)
        PHPhotoLibrary.shared().performChanges({
            PHAssetChangeRequest.deleteAssets(ImageManager.shared.deleteAssetList as NSFastEnumeration)

        }) { (is_success, error) in
            if is_success {
                DispatchQueue.main.async {
                    ImageManager.shared.deleteAssetList.removeAll()
                    AppManager.shared.userClick += ImageManager.shared.deleteAssetList.count
                    mediaBrowser.navigationController?.popViewController(animated: true)
                }
            }
        }
       

    }
    
    func gridCellSize() -> CGSize {
        return CGSize(width: (self.view.frame.width/3 - 5), height: (self.view.frame.width/3 - 5))
    }
   
    
}


extension HomeViewController: AlbumProtocol{
    func selectedAlbum(photos: PHFetchResult<PHAsset>) {
        self.imageCountLabel.text = "1/\(photos.count)"
        self.kolodaView.currentCardIndex = 0
        self.imageCount = 1
        self.addImagetoArray(photos: photos)
    }
}

Upvotes: 0

Views: 41

Answers (0)

Related Questions