I have a UIButton name as "Add new contact", When I click on this, a new contact screen is opened but when I tap on "Add Photo" then only "Choose Photo" is coming and "Take Photo" is not coming in iOS 11.
The code for new contact is -
let con = CNContact()
let vc = CNContactViewController(forNewContact: con)
vc.delegate = self
let navVC = UINavigationController(rootViewController: vc)
self.present(navVC, animated: true, completion: nil)
Related
I have a tableview that displays a sourceArray with a Name and Category properties. I want to filter the Category property of the sourceArray based on the users input from a UIAlertController but getting the error "Instance method 'contains' requires that 'UITextField' conform to 'StringProtocol'". Any help is greatly appreciated.
var sourceArray = [(Name: String, Category: String, Picture1: UIImage, Picture2: UIImage, Picture3: UIImage, Description: String)]()
#IBAction func filterButton(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Category", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "Filter", style: .default) { (action) in
print(textField)
let filtered = sourceArray.filter({$0.Category.contains(textField)})
self.filteredArray = filteredArray.isEmpty ? sourceArray : filtered
self.tableview.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Biceps, Chest, Shoulders, etc."
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
This issue I was having is that textField was not being recognized as a String. So I added a constant text as a String and assigned the textField.text to it like so.
#IBAction func filterButton(_ sender: UIBarButtonItem) {
var textField = UITextField()
let alert = UIAlertController(title: "Category", message: nil, preferredStyle: .alert)
let action = UIAlertAction(title: "Filter", style: .default) { (action) in
let text: String = textField.text!
let filtered = self.sourceArray.filter({$0.Category.contains(text)})
self.filteredArray = self.filteredArray.isEmpty ? self.sourceArray : filtered
self.tableview.reloadData()
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = "Biceps, Chest, Shoulders, etc."
textField = alertTextField
}
alert.addAction(action)
present(alert, animated: true, completion: nil)
}
I have a scroll view that has an image view embedded into it. The user clicks the "Take Photo" button, takes a picture, and then those photos are stored in an array and then displayed on the scrollable imageView. My issue is that after the didFinishSelectingMedia is called and the camera closes, the imageView always shows the first image in the array. I want the imageView to show the most recent image added to the array and scroll backward through the images until the user comes to the first image in the array. How do I make this happen?
Btw: Idk if this is even plausible, but I tried reversing the for loop and that didn't work.
I'm a new programmer, so please explain your answers.
Here's my code:
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate {
#IBOutlet weak var scrollImageView: UIScrollView!
var imageTaken: [UIImage] = []
var imagePicker = UIImagePickerController()
override func viewDidLoad() {
super.viewDidLoad()
scrollImageView.frame = scrollImageView.frame
}
#IBAction func takePhotoButton(_ sender: Any) {
imagePicker = UIImagePickerController()
imagePicker.delegate = self
imagePicker.sourceType = .camera
present(imagePicker, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
imageTaken.append((info[.originalImage] as? UIImage)!)
imagePicker.dismiss(animated: true, completion: nil)
for i in 0..<imageTaken.count {
let imageView = UIImageView()
imageView.image = imageTaken[i]
imageView.contentMode = .scaleAspectFit
let xPosition = self.scrollImageView.frame.width * CGFloat(i)
imageView.frame = CGRect(x: xPosition, y: 0, width: self.scrollImageView.frame.width, height: self.scrollImageView.frame.height)
scrollImageView.contentSize.width = scrollImageView.frame.width * CGFloat(i + 1)
scrollImageView.addSubview(imageView)
}
}
}
Two options I guess you can take which ever one is seems easier.
OOP — Create an array of objects example:
struct TakePhotos {
var image : UIImage
var timestamp : Date
}
Then sort the array by the timestamp.
Use PHAsset
import Photos
Using PHAsset you should be able to retrieve the images taken from the user's album. Set a count when the user takes a new image.
Then you can run a query and sort the images in reverse order, they will already have the timestamp.
let photoOptions = PHFetchOptions()
photoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
This is what I did in my application.
// FIXME: - Query only happens once this needs to be refactored so it happens when the album is updated on the device.
// Query only happens one time.
if imageArray.isEmpty {
let photoOptions = PHFetchOptions()
photoOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let allPhotos = PHAsset.fetchAssets(with: photoOptions)
let fetchOptions = PHImageRequestOptions()
fetchOptions.deliveryMode = .highQualityFormat
fetchOptions.resizeMode = .exact
fetchOptions.normalizedCropRect = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.width)
// Must be a synchronous call otherwise the view loads before images are retrieved (result is an empty view).
fetchOptions.isSynchronous = true
let imageSize : CGSize = CGSize(width: view.frame.width, height: view.frame.height)
//Retrieves Images using assets.
allPhotos.enumerateObjects { (assets, index, pointer) in
DispatchQueue.global(qos: .background).sync {
PHImageManager.default().requestImage(for: assets, targetSize: imageSize, contentMode: .aspectFit, options: fetchOptions) { (image, hashDictionary) in
guard let image = image else {return}
self.imageArray.append(image)
self.albumImages.append(ImageAlbum(images: image))
}
}
}
selectedImage.image = imageArray.first
selectedImage.contentMode = .scaleAspectFill
}
I am very lost when it comes to in app purchases. I found a very solid post on stack overflow that has been helpful. [Stack Post][1] I am still having issues after this though. If I have an in app purchase thats never been bought before and I tap the restore button it will unlock it anyway. Any help would be amazing thanks.
class ViewController: UIViewController, SKProductsRequestDelegate, SKPaymentTransactionObserver {
#IBOutlet weak var nonConsumableLabel: UILabel!
let id_sold = "id_sold"
var productID = ""
var productsRequest = SKProductsRequest()
var iapProducts = [SKProduct]()
var nonConsumablePurchaseMade = UserDefaults.standard.bool(forKey: "nonConsumablePurchaseMade")
override func viewDidLoad() {
super.viewDidLoad()
// Check your In-App Purchases
print("NON CONSUMABLE PURCHASE MADE: \(nonConsumablePurchaseMade)")
if nonConsumablePurchaseMade { nonConsumableLabel.text = "Premium version PURCHASED!"
} else { nonConsumableLabel.text = "Premium version LOCKED!"}
// Fetch IAP Products available
fetchAvailableProducts()
}
#IBAction func nonConsumableBtn(_ sender: Any) {
purchaseMyProduct(product: iapProducts[0])
}
#IBAction func printer(_ sender: Any) {
print(iapProducts)
}
// MARK: - RESTORE NON-CONSUMABLE PURCHASE BUTTON
#IBAction func restorePurchaseButt(_ sender: Any) {
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().restoreCompletedTransactions()
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully restored your purchase!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
for transaction in queue.transactions {
let t: SKPaymentTransaction = transaction
let prodID = t.payment.productIdentifier as String
switch prodID {
case "id_sold":
// implement the given in-app purchase as if it were bought
print("Second IAP restored")
default:
print("iap not found")
}
}
}
}
// MARK: - FETCH AVAILABLE IAP PRODUCTS
func fetchAvailableProducts() {
// Put here your IAP Products ID's
let productIdentifiers = NSSet(objects:
id_sold
)
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers as! Set<String>)
productsRequest.delegate = self
productsRequest.start()
}
// MARK: - REQUEST IAP PRODUCTS
func productsRequest (_ request:SKProductsRequest, didReceive response:SKProductsResponse) {
if (response.products.count > 0) {
iapProducts = response.products
// Get its price from iTunes Connect
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = .behavior10_4
numberFormatter.numberStyle = .currency
// 2nd IAP Product (Non-Consumable) ------------------------------
let secondProd = response.products[0] as SKProduct
// Get its price from iTunes Connect
numberFormatter.locale = secondProd.priceLocale
}
}
// MARK: - MAKE PURCHASE OF A PRODUCT
func canMakePurchases() -> Bool { return SKPaymentQueue.canMakePayments() }
func purchaseMyProduct(product: SKProduct) {
if self.canMakePurchases() {
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(self)
SKPaymentQueue.default().add(payment)
print("PRODUCT TO PURCHASE: \(product.productIdentifier)")
productID = product.productIdentifier
// IAP Purchases dsabled on the Device
} else {
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully restored your purchase!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
}
// MARK:- IAP PAYMENT QUEUE
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans = transaction as? SKPaymentTransaction {
switch trans.transactionState {
case .purchased:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
if productID == id_sold {
// Save your purchase locally (needed only for Non-Consumable IAP)
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
nonConsumableLabel.text = "Premium version PURCHASED!"
let alert = UIAlertController(title: "IAP Tutorial", message: "You've successfully unlocked the Premium version!", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
self.present(alert, animated: true)
}
break
case .failed:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
case .restored:
SKPaymentQueue.default().finishTransaction(transaction as! SKPaymentTransaction)
break
default: break
}
}
}
}
You are saying
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
nonConsumablePurchaseMade = true
UserDefaults.standard.set(nonConsumablePurchaseMade, forKey: "nonConsumablePurchaseMade")
}
But that method is called even if nothing was restored. All it means is that the restoration interaction is over, not that it succeeded. But you are going ahead and acting as if it succeeded, and awarding your user the magic user defaults key.
I've got a string array called languages, and I want to create as many UIAlertActions in a UIAlertController as there are elements in the array. I don't know how large the array will be, as the user can add languages to the array using the add option from the same UIAlertController.
loadLanguages() successfully loads userDefaults data into the languages array if there's any existing languages that've either been saved as the 2 starter languages on first app load and/or added by the user on subsequent uses of the app.
The add language option works, and is stored in userDfeaults (self.saveLanguages), and appended to the languages array.
However I'm not sure about creating UIAlertAction options for each language that is in the languages array. I've tried looping through the array to generate each menu item, as the languages array has the answer as to how many UIAlertActions should be displayed, but nothing appears.
After extensive searches I haven't come across anything covering this, but I'm sure there's an elegant approach.
FYI: languageChoiceButton is declared as:
var languageChoiceButton = UIAlertAction()
#objc func languageMenu(){
loadLanguages()
let chooseLanguageController = UIAlertController(title: "Vocabulary Tutor", message: "Choose a Language", preferredStyle: .actionSheet)
let addLanguage = UIAlertAction(title: "Add Language", style: .default, handler: { (action) -> Void in
let ac = UIAlertController(title: "Add a language", message: nil, preferredStyle: .alert)
ac.addTextField { textField in
textField.placeholder = "New language"
}
let submitAction = UIAlertAction(title: "Add", style: .default) { [unowned self, ac] (action: UIAlertAction!) in
self.newLanguage = ac.textFields?[0].text ?? ""
print("newLanguage: \(self.newLanguage)")
self.languages.append(self.newLanguage)
self.saveLanguages()
self.loadLanguages()
}
ac.addAction(submitAction)
ac.addAction(UIAlertAction(title: "Cancel", style: .cancel))
self.present(ac, animated: true)
})
chooseLanguageController.addAction(addLanguage)
for language in languages {
languageChoiceButton = UIAlertAction(title: language.capitalized, style: .default, handler: { (action) -> Void in
self.chosenLanguage = language
self.title = self.chosenLanguage.capitalized
print("Chosen language is: \(self.chosenLanguage)")
self.loadInitialValues()
chooseLanguageController.addAction(self.languageChoiceButton)
})
}
let cancel = UIAlertAction(title: "Cancel", style: .cancel) {
(action:UIAlertAction!) in
print("Cancel button tapped")
}
chooseLanguageController.addAction(cancel)
self.navigationController!.present(chooseLanguageController, animated: true, completion: nil)
}
Try to make an array while using certain languages and make a loop afterwards to add each of them to the alerts.
#objc func languageMenu(){
loadLanguages()
let chooseLanguageController = UIAlertController(title: "Vocabulary Tutor", message: "Choose a Language", preferredStyle: .actionSheet)
let i = languages.count - 1
for n in 0...i{
chooseLanguageController.addAction(UIAlertAction(title: arrayLanguage[n].language, style: .default, handler: { (action) in
print(self. languages[n])
}))
}
self.present(chooseLanguageController, animated: true, completion: nil)
}
I have been using the following code to call the UIActivityViewController for sharing stickers via the various social media apps:
if let image = sticker.getUIImage(), let imgData = UIImagePNGRepresentation(image) {
let activityVC = UIActivityViewController(activityItems: [imgData], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = gesture.view
self.present(activityVC, animated: true, completion: nil)
} else {
ErrorHandler.handleError(STICKER_IMAGE_NOT_FOUND, sticker)
}
This code has been working fine until the most recent update to FB messenger (version 98.0). Now it shows an error "Couldn't load content". FB messenger appears to prefer a URL like this:
if let item = sticker.getImageURL() {
let activityVC = UIActivityViewController(activityItems: [item], applicationActivities: nil)
activityVC.popoverPresentationController?.sourceView = gesture.view
self.present(activityVC, animated: true, completion: nil)
} else {
ErrorHandler.handleError(STICKER_IMAGE_NOT_FOUND, sticker)
}
This works fine with FB Messenger but iMessage displays the transparent PNG with a black background.
I was looking at UIActivityViewControllerCompletionWithItemsHandler but the discussion states it runs after the activity, too late for what I need to do. I also tried creating a custom UIActivity returning UIActivityType.message for activityType but it was added on to the bottom of the controller rather than taking over the default.
Is there a way to intercept the selection of the item in UIActivityViewController so I can use the MFMessageComposeViewController and add the UIImagePNGRepresentation to the message and allow all the others to use the URL?
Is there a particular argument type that I can pass to UIActivityViewController that will correctly display transparent PNG with all the social apps?
TIA
Mike
Circling back to close this up. I eventually switched from using the UIActivityViewController universally to a system that customizes what is done for each type of service. I use BDGShare by Bob de Graaf. I build a list of the services that the app supports, show a button for each and then a switch to jump to each type of share. Just in case someone's working on this, here's what I came up with:
The types of sharing the app wants to support:
public enum ShareServiceType:Int {
case iMessage=0, facebook, twitter, instagram, whatsApp, facebookMessenger, email, more
}
A class to store information about the type of sharing service
public class ShareTargetVO: NSObject
{
var icon:String!
var label:String!
var type:ShareServiceType
init( serviceType:ShareServiceType, icon:String, label:String )
{
self.type = serviceType
self.icon = icon
self.label = label
}
}
A function in my social networking helper to populate the list of available services:
static func getShareTargetList(for sticker : ReeSticker) -> [ShareTargetVO] {
var services:Array<ShareTargetVO> = []
// Check to see which capabilities are present and add buttons
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeFacebook)) {
services.append(ShareTargetVO(serviceType: ShareServiceType.facebook, icon: "Icon-Share-Facebook", label: "Facebook"))
}
// Checking for facebook service type because there's no availability check with FBSDK for messenger (could be rolled into the lib)
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeFacebook) && !sticker.type.doesContain("video")) {
services.append(ShareTargetVO(serviceType: ShareServiceType.facebookMessenger, icon: "Icon-Share-Messenger", label: "Messenger"))
}
if (BDGShare.shared().isAvailable(forServiceType: SLServiceTypeTwitter)) {
services.append(ShareTargetVO(serviceType: ShareServiceType.twitter, icon: "Icon-Share-Twitter", label: "Twitter"))
}
if (BDGShare.shared().canSendSMS()) {
services.append(ShareTargetVO(serviceType: ShareServiceType.iMessage, icon: "Icon-Share-Messages", label: "Messages"))
}
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://")! as URL) && !sticker.type.contains("video") {
services.append(ShareTargetVO(serviceType: ShareServiceType.whatsApp, icon: "Icon-Share-Whatsapp", label: "What's App?"))
}
if UIApplication.shared.canOpenURL(URL(string: "instagram://app")! as URL) && !sticker.type.contains("video") {
services.append(ShareTargetVO(serviceType: ShareServiceType.instagram, icon: "Icon-Share-Instagram", label: "Instagram"))
}
if (BDGShare.shared().canSendEmail()) {
services.append(ShareTargetVO(serviceType: ShareServiceType.email, icon: "Icon-Share-Mail", label: "Email"))
}
services.append(ShareTargetVO(serviceType: ShareServiceType.more, icon: "Icon-Share-More", label: "More"))
return services
}
A function in my view controller to populate a UICollectionView of buttons for sharing limited to those services that are returned from the list function:
func layoutShareButtons() {
let f = self.view.frame
let btnWidth = f.width * 0.82
let bannerWidth = btnWidth + 10
mask = UIView(frame: CGRect(x: 0, y: 0, width: f.width, height: f.height))
mask.backgroundColor = .black
mask.alpha = 0.3
self.view.addSubview(mask)
buttonList = SocialHelper.getShareTargetList(for: self.sticker)
let buttonGridLayout = UICollectionViewFlowLayout()
buttonGridLayout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
buttonGridLayout.itemSize = CGSize(width: 60, height: 60)
buttonGridLayout.scrollDirection = .horizontal
buttonListView = UICollectionView(frame: CGRect(x: (f.width - bannerWidth) / 2,
y: self.preview.frame.origin.y + self.preview.frame.height + 10,
width: bannerWidth,
height: 80),
collectionViewLayout: buttonGridLayout)
buttonListView.register(ShareButtonCell.self, forCellWithReuseIdentifier: "shareButtonCell")
buttonListView.dataSource = self
buttonListView.delegate = self
self.view.addSubview(buttonListView)
// cancel button for sharing view
// Button (added last to ensure it's on top of the z-order)
cancelButton = SimpleButton(frame: CGRect(x: (f.width - bannerWidth) / 2, y: self.buttonListView.frame.origin.y + self.buttonListView.frame.height + 10, width: bannerWidth, height: 52))
cancelButton.backgroundColor = UIColor(netHex:0x202020)
cancelButton.layoutComponent(0, label: "Cancel")
cancelButton.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.cancelButtonPressed(_:))))
self.view.addSubview(self.cancelButton)
}
UICollectionView didSelect handler (for better SoC depending on your app remove the "share" function to a separate class just for the share implementation, in this app the screen we're working with is specifically for share):
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.share(shareTarget: buttonList[indexPath.row])
}
Finally, a function call the correct share type:
func share(shareTarget: ShareTargetVO) {
// Params to submit to service
self.shareUrl = self.sticker.getStickerURL()
let textStr = "" // BDGShare supports a message passed in as well but we just send the sticker
// we need the NSData either way (sticker / video)
var ok = true
// try? is fine here because result is tested below
let urlData : Data? = try? Data(contentsOf: self.shareUrl as URL)
ok = (urlData != nil)
var img: UIImage? = nil
// if it's an image type then get it
if ok {
if (self.shareUrl.pathExtension.contains("png")) || (self.shareUrl.pathExtension.contains("jpg")) {
img = UIImage(data: urlData! as Data)
ok = (img != nil)
}
}
if !ok {
let alertCtrl = UIAlertController(title: "Error sending", message: "There was an error gathering the information for sending this sticker.", preferredStyle: .alert)
alertCtrl.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alertCtrl, animated: true, completion: nil)
return
}
switch shareTarget.type
{
case .iMessage:
BDGShare.shared().shareSMS(textStr, recipient: nil, image: img, data:urlData! as Data, imageName: "sendSticker.png", completion: {(SharingResult) -> Void in
// Handle share result...
self.handleShareResult(shareTarget.type, shareResult: SharingResult)
})
break
case .facebook:
BDGShare.shared().shareFacebook("", urlStr: "", image: img, completion: {(SharingResult) -> Void in
// Handle share result...
self.handleShareResult(shareTarget.type, shareResult: SharingResult)
})
break
case .twitter:
... more code for handling each type of share
}
}
So there are all the pieces I used to implement using BDGShare to get around the UIActivityViewController. BTW - the "More" option at the end of the list calls that UIActivityViewController so it's still available to the user if they so choose.
HTH, Mike