IOS application freezes after link share - ios

I am new to ios development. I am doing my first project and I want to implement a small application that will allow saving favorite links from the browser. I am using SwiftUI for main app + share extension component to pass link to main app from browser.
The flow looks like this: I find an interesting site, click the Share button and select the icon for my application. After that, my application opens and there I see my link. This works successfully, but after that the browser freezes completely. If I post a link from other apps, the same thing happens.
Below is the code of the share extension and the component of the main application that reads the link:
#objc(ShareExtensionViewController)
class ShareViewController: UIViewController {
private static let URL_STORAGE_KEY = "url"
private static let APP_URL = "MyApp://"
private static let APP_GROUP_NAME = "group.ru.myapp"
override func viewDidLoad() {
super.viewDidLoad()
// set link to UserDefaults
handleShared()
// open main app from extension
openMainApp()
}
#objc
#discardableResult
func openURL(_ url: URL) -> Bool {
var responder: UIResponder? = self
while responder != nil {
if let application = responder as? UIApplication {
return application.perform(#selector(openURL(_:)), with: url) != nil
}
responder = responder?.next
}
return false
}
private func openMainApp() {
DispatchQueue.global(qos: .background).async {
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: { _ in
let url = URL(string: ShareViewController.APP_URL)!
self.openURL(url)
})
}
}
private func handleShared() {
let attachments = (extensionContext?.inputItems.first as? NSExtensionItem)?.attachments ?? []
let contentType = kUTTypeURL as String
for provider in attachments {
if provider.hasItemConformingToTypeIdentifier(contentType) {
provider.loadItem(forTypeIdentifier: contentType, options: nil) {
[unowned self] (data, error) in
guard error == nil else {
return
}
let userDefaults = UserDefaults(suiteName: ShareViewController.APP_GROUP_NAME)!
userDefaults.set(data, forKey: ShareViewController.URL_STORAGE_KEY)
}
}
}
}
}
Snippet of the main application code:
static func tryGetLink() -> String? {
let userDefaults = UserDefaults(suiteName: DataStorage.APP_GROUP)
return userDefaults?.object(forKey: URL_KEY) as? String
}
What could be the reason for these freezes?

Related

Share Extension not working when I long press a link

I've built a sharing extension for my iOS app. The goal is to store all URLs user shared in UserDefaults variable. Here is my code:
import UIKit
import Social
import MobileCoreServices
class ShareViewController: SLComposeServiceViewController {
override func viewDidLoad() {
let extensionItem = extensionContext?.inputItems.first as! NSExtensionItem
let itemProvider = extensionItem.attachments?.first as! NSItemProvider
let propertyList = String(kUTTypePropertyList)
if itemProvider.hasItemConformingToTypeIdentifier(propertyList) {
itemProvider.loadItem(forTypeIdentifier: propertyList, options: nil, completionHandler: { (item, error) -> Void in
guard let dictionary = item as? NSDictionary else { return }
OperationQueue.main.addOperation {
if let results = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as? NSDictionary,
let urlString = results["URL"] as? String,
let url = NSURL(string: urlString) {
print("URL retrieved: \(urlString)")
let userDefaults = UserDefaults(suiteName: "group.my.app")
if userDefaults?.object(forKey: "extensionArticles") == nil {
userDefaults?.setValue([urlString], forKey: "extensionArticles")
} else {
var urls = userDefaults?.object(forKey: "extensionArticles") as? [String]
urls?.append(urlString)
print("im here in the extension")
dump(urls)
userDefaults?.setValue(urls, forKey: "extensionArticles")
}
}
}
})
} else {
print("error")
}
Timer.scheduledTimer(timeInterval: 0.2, target: self, selector: #selector(self.didSelectPost), userInfo: nil, repeats: false)
}
override func isContentValid() -> Bool {
// Do validation of contentText and/or NSExtensionContext attachments here
return true
}
override func didSelectPost() {
// This is called after the user selects Post. Do the upload of contentText and/or NSExtensionContext attachments.
print("posted")
// Inform the host that we're done, so it un-blocks its UI. Note: Alternatively you could call super's -didSelectPost, which will similarly complete the extension context.
self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
}
override func configurationItems() -> [Any]! {
// To add configuration options via table cells at the bottom of the sheet, return an array of SLComposeSheetConfigurationItem here.
return []
}
}
When I go to the webpage in Safari and share it being on that page, everything works fine. However, when I long press (aka 3d touch) on a link in Safari, and use my extension from there, it won't add a link to UserDefaults. I couldn't find how to fix this, plz help 😭

How to share custom stickers from my app to telegram app through its APIs with Swift?

I am looking for a clean way to share my custom stickers from my application using telegram APIs.
I have been searching telegram documentation about how to send custom stickers with the proper way to be read through telegram.
I found here how to create the sticker set -> https://core.telegram.org/bots/api#stickerset,
and here how to make the stickers itself -> https://core.telegram.org/bots/api#stickerset
This is my code for preparing the stickerSet and stickers
func handleStickersForTelegram(completionHandler: #escaping (Bool) -> Void) {
StickerPackManager.queue.async {
var json: [String: Any] = [:]
json["name"] = self.name
json["title"] = self.publisher
json["contains_masks"] = false
if self.animated == true {
json["is_animated"] = true
}
var stickersArray: [[String: Any]] = []
for sticker in 1...self.stickers.count {
var stickerDict: [String: Any] = [:]
print("stickerTest", self.stickers.count )
stickerDict["file_id"] = "\(StickerBaseURL)/\(self.identifier)/\(sticker).webp"
stickerDict["set_name"] = self.name
stickerDict["file_unique_id"] = "\(self.identifier)/\(sticker).webp"
stickerDict["width"] = 96
stickerDict["height"] = 96
if self.animated == true {
stickerDict["is_animated"] = true
}
stickersArray.append(stickerDict)
}
json["stickers"] = stickersArray
let result = Interoperability.sendToTelegram(json: json)
DispatchQueue.main.async {
completionHandler(result)
}
}
}
And after calling Interoperability.sendToTelegram(json:json)I have tried to make the function the way I did for sharing custom stickers to Whatsapp but didn't find the proper information to do that here is my function for whatsapp sharing.
struct Interoperability {
private static let DefaultBundleIdentifier: String = "MyBundleIdentifier"
private static let PasteboardExpirationSeconds: TimeInterval = 60
private static let PasteboardStickerPackDataType: String = "net.whatsapp.third-party.sticker-pack"
private static let WhatsAppURL: URL = URL(string: "whatsapp://stickerPack")!
static func send(json: [String: Any]) -> Bool {
if let bundleIdentifier = Bundle.main.bundleIdentifier {
if bundleIdentifier.contains(DefaultBundleIdentifier) {
fatalError("Your bundle identifier must not include the default one.");
}
}
let pasteboard: UIPasteboard = UIPasteboard.general
var jsonWithAppStoreLink: [String: Any] = json
jsonWithAppStoreLink["ios_app_store_link"] = iOSAppStoreLink
jsonWithAppStoreLink["android_play_store_link"] = AndroidStoreLink
guard let dataToSend = try? JSONSerialization.data(withJSONObject: jsonWithAppStoreLink, options: []) else {
return false
}
if #available(iOS 10.0, *) {
pasteboard.setItems([[PasteboardStickerPackDataType: dataToSend]], options: [UIPasteboard.OptionsKey.localOnly: true, UIPasteboard.OptionsKey.expirationDate: NSDate(timeIntervalSinceNow: PasteboardExpirationSeconds)])
} else {
pasteboard.addItems([[PasteboardStickerPackDataType: dataToSend]])
}
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(URL(string: "whatsapp://")!) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(WhatsAppURL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(WhatsAppURL)
}
}
}
return true
}
}
This my approach for making the same for telegram but didn't work!
I have added to my struct above
struct Interoperability {
private static let stickerSetDataType: String = "stickers.createStickerSet"
private static let testTelegram: URL = URL(string: "tg://StickerSet")!
static func sendToTelegram(json: [String: Any]) -> Bool {
if let bundleIdentifier = Bundle.main.bundleIdentifier {
if bundleIdentifier.contains(DefaultBundleIdentifier) {
fatalError("Your bundle identifier must not include the default one.");
}
}
let pasteboard: UIPasteboard = UIPasteboard.general
guard let dataToSend = try? JSONSerialization.data(withJSONObject: json, options: []) else {
return false
}
pasteboard.addItems([[stickerSetDataType: dataToSend]])
DispatchQueue.main.async {
if UIApplication.shared.canOpenURL(URL(string: "tg://")!) {
if #available(iOS 10.0, *) {
UIApplication.shared.open(testTelegramURL, options: [:], completionHandler: nil)
} else {
UIApplication.shared.openURL(testTelegramURL)
}
}
}
return true
}
}
It opens the telegram but nothing happens no stickers there is no sign that telegram read my stickers.
I don't know if I saved the stickers data in UIPasteboard with a wrong way or these info are wrong.
private static let stickerSetDataType: String = "stickers.createStickerSet"
private static let testTelegram: URL = URL(string: "tg://StickerSet")!
Telegram app not work like whatsapp. you need to add your sticker package on telegram by following this link
https://www.makeuseof.com/tag/how-to-make-telegram-stickers/
Once you add your package on telegram then you can use your package name to add sticker package on telegram by using following code
if let url = URL(string: "https://telegram.me/addstickers/ClassicAlice") {
if #available(iOS 10.0, *) {
UIApplication.shared.open(url)
} else {
UIApplication.shared.openURL(url)
}
}

Globally save and access received data

I'm trying to get some data from the server and use it globally in the app..
I mean for example, I'm using following code to get data from service:
struct Service : Decodable{
let id: Int
let name, description: String
let createdAt: String?
let updatedAt: String?
}
func makeGetCall() {
let todoEndpoint: String = "http://web.src01.view.beta.is.sa/public/api/services"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
guard error == nil else {
print("error calling GET on /public/api/services")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let todos = try decoder.decode([Service].self, from: responseData)
for todo in todos{
print(todo.name)
}
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
This code is located and called in HomeViewController and i'm getting data which i want.
But i want to access and use this data in another viewcontroller and in whole app...
How i can do it? How can i make the received data from the function is saved globally and how to use it in another viewcontroller?
Can someone tell me how i can do this?
For such cases we usually use static data. They may be served as singleton or just a static property. In your case a static property for cached data may be nice. We can put static properties in extension so adding following may be nice:
// MARK: - Fetching Data
extension Service {
private static var cachedServices: [Service]?
static func fetchServices(_ completion: (_ services: [Service]) -> Void?) {
if let cachedServices = cachedServices {
completion(cachedServices)
} else {
makeGetCall { services in
let newServices = services ?? []
self.cachedServices = newServices
completion(newServices)
}
}
}
}
Now the usage from everywhere is calling
Service.fetchServices { services in
}
and this call may be asynchronous or not, depending if data is already loaded.
If you need to access them synchronous and you are sure data is already loaded then simply add another method in extension:
static func getCachedData() -> [Service] {
return cachedServices ?? []
}
This method will return instantly but array will be empty if no data was received yet. But anywhere you can call Service.getCachedData()
This cache is now only preserved until your app terminates. If you want to preserve them longer then all you need to do is add the logic to save and load data into file or user defaults. The logic for that would be something like:
private static var cachedServices: [Service]? {
didSet {
self.saveServicesToFile(cachedServices)
}
}
static func fetchServices(_ completion: (_ services: [Service]) -> Void?)
{
if let cachedServices = cachedServices {
completion(cachedServices)
} else if let saved = self.loadFromFile() {
self.cachedServices = saved
completion(saved)
}else {
makeGetCall { services in
let newServices = services ?? []
self.cachedServices = newServices
completion(newServices)
}
}
}

Completion Handlers in swift 4

I have a problem I'm trying to wrap my head around relating to the use of completion handlers. I have 3 layers in my iOS program, the ViewController->Service->Networking. I need to load some data through API call from the view controller.
I have defined functions(completionHandlers) in the ViewController that should execute once the data request is complete and am comfortable when in implementing completion handlers when only two layers exists, but confused when in the following scenario:
DashboardViewController.swift
import UIKit
#IBDesignable class DashboardViewController: UIViewController {
#IBOutlet weak var stepCountController: ExpandedCardView!
var articles:[Article]?
let requestHandler = RequestHandler()
let dashboardService = DashboardService()
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.getDashboardData(completionHandler: getDashboardDataCompletionHandler)
}
func getDashboardDataCompletionHandler(withData: DashboardDataRequest) {
print(withData)
}
}
DashboardService.swift
import Foundation
class DashboardService: GeneralService {
var requestHandler: DashboardRequestHandler
override init() {
requestHandler = DashboardRequestHandler()
super.init()
}
//this function should execute requestHandler.requestDashboardData(), and then execute convertDashboardData() with the result of previous get request
func getDashboardData(completionHandler: #escaping (DashboardDataRequest) -> Void) {
//on network call return
guard let url = URL(string: apiResourceList?.value(forKey: "GetDashboard") as! String) else { return }
requestHandler.requestDashboardData(url: url, completionHandler: convertDashboardData(completionHandler: completionHandler))
}
func convertDashboardData(completionHandler: (DashboardDataRequest) -> Void) {
//convert object to format acceptable by view
}
}
DashboardRequestHandler.swift
import Foundation
class DashboardRequestHandler: RequestHandler {
var dataTask: URLSessionDataTask?
func requestDashboardData(url: URL, completionHandler: #escaping (DashboardDataRequest) -> Void) {
dataTask?.cancel()
defaultSession.dataTask(with: url, completionHandler: {(data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else {
return
}
do {
let decodedJson = try JSONDecoder().decode(DashboardDataRequest.self, from: data)
completionHandler(decodedJson)
} catch let jsonError {
print(jsonError)
}
}).resume()
}
}
If you look at the comment in DashboardService.swift, my problem is quite obvious. I pass a completion handler from ViewController to Service and Service has its own completion handler that it passes to RequestHandler where the view controller completion handler (getDashboardDataCompletionHandler) should be executed after the Service completion handler (convertDashboardData())
Please help me in clarifying how to implement this. Am I making a design mistake by trying to chain completionHandlers like this or am I missing something obvious.
Thank you
--EDIT--
My Request Handler implementation is as follows:
import Foundation
class RequestHandler {
// let defaultSession = URLSession(configuration: .default)
var defaultSession: URLSession!
init() {
guard let path = Bundle.main.path(forResource: "Api", ofType: "plist") else {
print("Api.plist not found")
return
}
let apiResourceList = NSDictionary(contentsOfFile: path)
let config = URLSessionConfiguration.default
if let authToken = apiResourceList?.value(forKey: "AuthToken") {
config.httpAdditionalHeaders = ["Authorization": authToken]
}
defaultSession = URLSession(configuration: config)
}
}
In this case is more clear to use delegation, for example like this:
protocol DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel)
}
class DashboardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
dashboardService.delegate = self
dashboardService.getDashboardData()
}
}
extension DashboardViewController: DashboardServiceDelegate {
func didLoaded(_ viewModel: DashboardViewModel) {
///
}
}
protocol DashboardRequestHandlerDelegate() {
func didLoaded(request: DashboardDataRequest)
}
class DashboardService: GeneralService {
private lazy var requestHandler: DashboardRequestHandler = { reqHandler in
reqHandler.delegate = self
return reqHandler
}(DashboardRequestHandler())
func getDashboardData() {
guard let url = ...
requestHandler.requestDashboardData(url: url)
}
}
extension DashboardService: DashboardRequestHandlerDelegate {
func didLoaded(request: DashboardDataRequest) {
let viewModel = convert(request)
delegate.didLoaded(viewModel)
}
}

Xcode 6.3.2 - can't get authorization from Facebook via Parse.com - Swift

Rebuilding an app from Xcode 6.2 to 6.3.2
Parse linking is working, as tested with their dummy code from guide.
I think the problem is in the authorization's request, something must be changed in the way Parse gets access to user info, but can't solve where.
Update HINT 1:
if I sign up, no problem, but if I stop and re-open the app, the problem is there, since auto-complete on saveInBackgroundWithBlock failed, there could be a problem there, but where?
Update HINT 2:
I see on Parse's site, that "sessions" keep growing each time i log in
got this error:
the code in which I request authorizations:
import UIKit
import Parse
class LoginViewController: UIViewController, FBLoginViewDelegate {
#IBOutlet weak var fbLoginView: FBLoginView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func pressedLoginFB(sender: UIButton) {
//MARK: facebook personlized login from 440
//to start, request permissions
PFFacebookUtils.logInWithPermissions(["public_profile", "user_about_me", "user_birthday"], block: {
user, error in
if user == nil
{
println("BAD! user cancelled login")
//add a uialertcontrolelr before pushing to app store
return
}
else if user!.isNew
{
println("GOOD! - User signed up with Facebook")
//now, ask facebook for user's data
FBRequestConnection.startWithGraphPath("/me?fields=picture,first_name,birthday", completionHandler: {
connection, result, error in
var resultDictionary = result as! NSDictionary
user!["firstName"] = resultDictionary["first_name"]
user!["gender"] = resultDictionary["gender"]
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd/MM/YYYY"
user!["birthday"] = dateFormatter.dateFromString(resultDictionary["birthday"] as! String)
let pictureURL = ((resultDictionary["picture"] as! NSDictionary)["data"] as! NSDictionary) ["url"] as! String
let url = NSURL(string: pictureURL)
let request = NSURLRequest(URL: url!) //in old Xcode was an optional
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
let imageFile = PFFile(name: "avatar.jpg", data: data)
user!["picture"] = imageFile as PFFile //MARK: not sure about this downcast
user!.saveInBackgroundWithBlock({
success, error in
println("hello")
println(success)
println(error)
})
})
}
)
}
else
{
println("GOOD - User logged in with Facebook")
}
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("mapNavVCID") as! UIViewController
self.presentViewController(vc, animated: true, completion: nil)
})
}
}
this is my personal user struct, made for decoupling Parse:
import Foundation
import MapKit
import Parse
struct User {
let id: String
// let pictureUrl: String
let name: String
private let pfUser :PFUser //pfUser is private because I will use it only with our backend
//this is a nested function, I use this design pattern because of getDataInBackgroundWithBlock
func getPhoto(callback:(UIImage) -> ()) {
let imageFile = pfUser.objectForKey("picture") as! PFFile
imageFile.getDataInBackgroundWithBlock({
data, error in
if let data = data{
callback(UIImage(data: data)!)
}
})
}
}
private func pfUserToUser(user: PFUser) -> User {
return User(id: user.objectId!, name: user.objectForKey("firstName") as! String , pfUser: user)
// I cut off this:
// "pictureUrl: user.objectForKey("picture") as String," because now it is an image thanks to gePhoto function
}
//test on optionals "if user exists, give the value to the user constant,
//and return a User instance via pfUserToUser function"
// the "-> User?" is the reason why I have an optional in loginViewController on " currentUser()? "
func currentUser() -> User? {
if let user = PFUser.currentUser() {
return pfUserToUser(user)
}
else {
//if optional user doesn't exist, return nil
return nil
}
}
thanks in advance

Resources