Search code examples
iosswiftobjective-cattachmentuialertaction

How to show "HSAttachmentPicker" in iPad in swift


I need to upload images and pdf files so i am using HSAttachmentPicker framework with this i am able to open picker options and able to upload image and pdf in iPhone but its crashing in iPad when i click upload button

error:

You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.'

but HSAttachmentPicker is a framework so where should i add code for iPad

code: this is the code i have written in my upload image class

    let picker = HSAttachmentPicker()

    @IBAction func uploadButtonTapped(_ sender: UIButton) {
    picker.delegate = self
    picker.showAttachmentMenu()
    }

extension PostEnquiryViewController: HSAttachmentPickerDelegate {
func attachmentPickerMenu(_ menu: HSAttachmentPicker, showErrorMessage errorMessage: String) {
}

func attachmentPickerMenuDismissed(_ menu: HSAttachmentPicker) {
}

func attachmentPickerMenu(_ menu: HSAttachmentPicker, show controller: UIViewController, completion: (() -> Void)? = nil) {
    self.present(controller, animated: true, completion: completion)
}

func attachmentPickerMenu(_ menu: HSAttachmentPicker, upload data: Data, filename: String, image: UIImage?) {

    if !data.isEmpty && (filename as NSString).pathExtension == "pdf" {
        self.attachmentFiles.append(.init(data: data, fileName: filename))
        self.attachmentImages.append(.init(image: UIImage(named: "pdf")!, imageName: filename))
        self.uploadFileImage.image = UIImage(named: "pdf")
    }
    if let img = image {
        DispatchQueue.main.async {
            self.uploadFileImage.image = img
        }
        self.attachmentImages.append(.init(image: img, imageName: filename))
    }
}
}

here is the code for showAttachmentMenu in HSAttachmentPicker framework file: here where to add code for iPad.. please guide.

  - (void)showAttachmentMenu {
self.selfReference = self;
UIAlertController *picker = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
NSString *showPhotosPermissionSettingsMessage = [NSBundle.mainBundle objectForInfoDictionaryKey:@"NSPhotoLibraryUsageDescription"];
if ([UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypeCamera] && showPhotosPermissionSettingsMessage != nil) {
    UIAlertAction *takePhotoAction = [UIAlertAction actionWithTitle:[self translateString:@"Take Photo"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        if (@available(iOS 14.0, *)) {
            [self validatePhotosPermissionsWithAccessLevel:PHAccessLevelAddOnly completion:^{
                [self showImagePicker:UIImagePickerControllerSourceTypeCamera];
            }];
        } else {
            [self validatePhotosPermissions:^{
                [self showImagePicker:UIImagePickerControllerSourceTypeCamera];
            }];
        }
    }];
    [picker addAction:takePhotoAction];
}

if (showPhotosPermissionSettingsMessage != nil) {
    if (@available(iOS 14.0, *)) {
        // the app already has access to the Photo Library so we're safe to add `Use Last Photo` here
        if ([PHPhotoLibrary authorizationStatusForAccessLevel:PHAccessLevelReadWrite] == PHAuthorizationStatusAuthorized) {
            UIAlertAction *useLastPhotoAction = [UIAlertAction actionWithTitle:[self translateString:@"Use Last Photo"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
                [self useLastPhoto];
            }];
            [picker addAction:useLastPhotoAction];
        }
    } else {
        UIAlertAction *useLastPhotoAction = [UIAlertAction actionWithTitle:[self translateString:@"Use Last Photo"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
            [self validatePhotosPermissions:^{
                [self useLastPhoto];
            }];
        }];
        [picker addAction:useLastPhotoAction];
    }
}

if (showPhotosPermissionSettingsMessage != nil) {
    UIAlertAction *chooseFromLibraryAction = [UIAlertAction actionWithTitle:[self translateString:@"Choose from Library"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        if (@available(iOS 14, *)) {
            // don't request access to users photo library since we don't need it with PHPicker
            [self showPhotoPicker];
        } else {
            [self validatePhotosPermissions:^{
                [self showImagePicker:UIImagePickerControllerSourceTypePhotoLibrary];
            }];
        }
    }];
    [picker addAction:chooseFromLibraryAction];
}

UIAlertAction *importFileFromAction = [UIAlertAction actionWithTitle:[self translateString:@"Import File from"] style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
    [self showDocumentPicker];
}];
[picker addAction:importFileFromAction];

UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:[self translateString:@"Cancel"] style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
    [self dismissed];
}];

[picker addAction:cancelAction];

[self.delegate attachmentPickerMenu:self showController:picker completion:nil];
}

Solution

  • It would be a good idea to read-up on popover presentation: Docs

    If you look at the view controller in the example provided with HSAttachmentPicker, you'll see how the example app handles it. Yes, it's in Objective-C ... but it's almost identical in Swift:

    func attachmentPickerMenu(_ menu: HSAttachmentPicker, show controller: UIViewController, completion: (() -> Void)? = nil) {
    
        // popover will be non-nil if we're on an iPad
        if let popover = controller.popoverPresentationController {
            
            // we want the popover arrow pointing to the button
            popover.sourceView = self.uploadButton
            
        }
    
        self.present(controller, animated: true, completion: completion)
    }
    

    So, here's a complete, runnable example:

    import UIKit
    
    class ViewController: UIViewController {
    
        let uploadButton = UIButton()
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            view.backgroundColor = .systemYellow
            
            uploadButton.setTitle("Tap to Upload", for: [])
            uploadButton.setTitleColor(.white, for: .normal)
            uploadButton.setTitleColor(.lightGray, for: .highlighted)
            uploadButton.backgroundColor = .systemBlue
            uploadButton.layer.cornerRadius = 8.0
            
            uploadButton.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(uploadButton)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                // let's put the button near the bottom of the screen
                uploadButton.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -40.0),
                uploadButton.widthAnchor.constraint(equalToConstant: 200.0),
                uploadButton.heightAnchor.constraint(equalToConstant: 60.0),
                uploadButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ])
            
            uploadButton.addTarget(self, action: #selector(uploadButtonTapped(_:)), for: .touchUpInside)
        }
    
        @IBAction func uploadButtonTapped(_ sender: UIButton) {
            let picker = HSAttachmentPicker()
            picker.delegate = self
            picker.showAttachmentMenu()
        }
    }
    
    extension ViewController: HSAttachmentPickerDelegate {
        func attachmentPickerMenu(_ menu: HSAttachmentPicker, showErrorMessage errorMessage: String) {
            // Handle errors
        }
        
        func attachmentPickerMenuDismissed(_ menu: HSAttachmentPicker) {
            // Run some code when the picker is dismissed
        }
        
        func attachmentPickerMenu(_ menu: HSAttachmentPicker, show controller: UIViewController, completion: (() -> Void)? = nil) {
    
            // popover will be non-nil if we're on an iPad
            if let popover = controller.popoverPresentationController {
                
                // we want the popover arrow pointing to the button
                popover.sourceView = self.uploadButton
                
            }
    
            self.present(controller, animated: true, completion: completion)
        }
        
        func attachmentPickerMenu(_ menu: HSAttachmentPicker, upload data: Data, filename: String, image: UIImage?) {
            // Do something with the data of the selected attachment, i.e. upload it to a web service
        }
    }