Link to multiple websites - swift

I want to create a link, but when pressed it randomly chooses a link from a list. I have the code to take the button a link already, but how how would i modify it so I add more urls to it randomly choose when clicked different times.
Current link code:
#IBAction func Website(_ sender: Any) {
if let url = NSURL(string: "http:heeeeeeeey.com/"){
UIApplication.shared.openURL(url as URL)
}
}

Try something like:
#IBAction func Website(_ sender: Any) {
let websites = ["http://website1.com",
"http://website2.com",
"http://website3.com"]
if let url = URL(string: websites[Int(arc4random_uniform(UInt32(websites.count)))]) {
UIApplication.shared.openURL(url as URL)
}
}
This should do the trick for you.
Explanation:
websites is an array of Strings, so put all the URLs you want the button to pick from in there.
Int(arc4random_uniform(UInt32(websites.count))) is the magical part that picks a random number between 0 and websites.count(which is the last item you have in the array.
If you are new to Swift (or programming), this might sound confusing to you, don't freak out, just keep practicing.
Best of luck!

First, to create an array of URL strings is pretty straightforward:
var urls = [
"http://www.url1.com",
"http://www.url2.com",
"http://www.url3.com"
]
Now, you could get a random element of this urls array with this long line of code:
let randomURL = urls[Int(arc4random_uniform(UInt32(urls.count)))]
However, another way you could do it is to add an extension to Array that works on all arrays:
extension Array {
public var random: Element? {
let index = Int(arc4random_uniform(UInt32(self.count)))
return self.count>0 ? self[index] : nil
}
}
Now, getting a random element of the urls array is as easy as:
urls.random
This returns an Optional (this is because if there are no elements in an array, the random property will return nil). So in your code you'll also need to unwrap the result of the random property:
#IBAction func Website(_ sender: Any) {
if let urlString = urls.random,
let url = URL(string: urlString) {
UIApplication.shared.openURL(url as URL)
}
}
P.S. A couple of comments on your code:
I recommend you rename Website to openRandomWebsite (remembering to change the storyboard connections too). Methods should explain what they do, and begin with a lower case letter. If you're interested, Swift general code conventions here.
The openURL method has been deprecated in iOS 10, so I recommend you use the open(_:​options:​completion​Handler:​) method.
Your code would look like:
UIApplication.shared.open(url, options: [:], completionHandler: { (success) in
//URL opened
})

Related

Is there a faster way to loop through the installed applications on macOS?

I'm writing a menu bar extra that shows you a list of your installed apps and allows you to click on each button in the list to open that app. Obviously, to do this I need a list of every app the user has. The specific way I chose to do this was making a function that would loop through the files in the system's Applications folder, strip out anything in an app's contents or that didn't end in .app, and return an array containing a list of files as names, which is then iterated through to create a list of "app buttons" that the user can click on to launch the app.
The code for my function is
func enumerateAppsFolder() -> Array<String> {
var fileNames:Array<String> = []
let fileManager = FileManager.default
let enumerator:FileManager.DirectoryEnumerator = fileManager.enumerator(atPath:"/Applications/")!
while let element = enumerator.nextObject() as? String {
if element.hasSuffix("app") && !element.contains("Contents") { // checks the extension
fileNames.append(element)
}
}
return fileNames
}
And I create my list with
ForEach(enumerateAppsFolder(), id:\.self){
AppBarMenuItem(itemAppName: $0)
}
But when I do it like that, the result is what I expected, but the performance is horrible. This can be seen in the screenshot, and will just be made worse by larger applications folders on some people's systems
(When the app is starting up, which takes about 5 minutes, the CPU and disk usage are also extremely high)
Is there a better and faster method that will retrieve every app on the system, similarly to the macOS launchpad or "Open With.." list?
The enumerator method of FileManager that you are using performs a deep enumeration of the file tree. You don't want a deep enumeration, just a top-level enumeration. Use the version of the enumerator method that has the options parameter and pass in .skipsSubdirectoryDescendants.
Here's an updated version of your function getting a URL directly from FileManager for the Applications folder and then doing a shallow enumeration to get the list of apps.
func enumerateAppsFolder() -> [String] {
var appNames = [String]()
let fileManager = FileManager.default
if let appsURL = fileManager.urls(for: .applicationDirectory, in: .localDomainMask).first {
if let enumerator = fileManager.enumerator(at: appsURL, includingPropertiesForKeys: nil, options: .skipsSubdirectoryDescendants) {
while let element = enumerator.nextObject() as? URL {
if element.pathExtension == "app" { // checks the extension
appNames.append(element.deletingPathExtension().lastPathComponent)
}
}
}
}
return appNames
}
print(enumerateAppsFolder())
Sample output when run from a Swift Playground:
"Numbers", "Dropbox", "Xcode", "Apple Configurator 2", "iMovie"

SWIFT Set UI Value following API Function Call

Apologies this is probably a real newbie question. I have some API code which goes away and calls a web service. When it returns I'd like to grab the value and display it in the UI but I'm struggling to see what means allows me to do this. I have this so far in my ViewController.....
'''
#IBAction func getToken(_ sender: NSButton) {
let cli = WSTokenServiceClient()
let rq = SecurityTokenRequest()
rq.UserName = "byname"
rq.Password = "abcdefg"
cli.GetUserWSToken(securityTokenRequest: rq, completionHandler: {(rep, err) in
NSLog(rep?.xmlResponseString ?? "none") //This successfully contains the string value I want
//token.stringValue = "jim" ///This doesn't work, it errors: NSControl.stringValue must be used from main thread only
})
}'''
I'd be grateful for any tips of where to go to solve this.
Thanks
You need
DispatchQueue.main.async {
self.token.stringValue = "jim"
}

MacOS NSWorkspace.shared.open "The application can’t be opened. -50"

Following some other Stack Overflow code examples of NSWorkspace.shared.open(), I came up with this:
#IBAction func mailFileVacuum(_ sender: NSButton) {
let receiver = sender.alternateTitle
let sendAddress = String(format: "mailto:%##filevacuum.com?subject=FileVacuum %#", receiver, receiver).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
NSLog("receiver %#", receiver)
let mailUrl = URL(string: sendAddress)
NSLog("mailUrl %#", mailUrl!.absoluteString)
if NSWorkspace.shared.open(mailUrl!) {
print("Default browser was successfully opened to send email. 😊📬")
}
}
☝️ that gets us this 👇
I tried changing the default browser, that's not the issue.
NSLog(mailUrl) logs this mailto%3AFeedback%40filevacuum.com%3Fsubject=FileVacuum%20Feedback
Look good maybe 🤔...
¿Que Paso?
You should not be percent encoding the entire URL; but rather, its individual components. See the Apple URL Scheme Reference.
The correctly formatted URL should look like (i.e. NSLog(mailUrl) should output):
mailto:Feedback#filevacuum.com?subject=FileVacuum%20Feedback

Unexpectedly unwrapping an optional to find a nil after an API call to Spotify

So I know this may be a bit specific but I've been staring at my code and am unable to resolve this issue. Basically, I'm making a network call to spotify to obtain a certain playlist and pass a number that will ultimately determine the number of songs I get back. The code is basically as follows:
// A network call is made just above to return somePlaylist
let playlist = somePlaylist as! SPTPartialPlaylist
var songs: [SPTPartialTrack] = []
// load in playlist to receive back songs
SPTPlaylistSnapshot.playlistWithURI(playlist.uri, session: someSession) { (error: NSError!, data: AnyObject!) in
// cast the data into a correct format
let playlistViewer = data as! SPTPlaylistSnapshot
let playlist = playlistViewer.firstTrackPage
// get the songs
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(playlist.items.count)))
songs.append(playlist.items[random] as! SPTPartialTrack)
}
}
The problem comes at the portion of code that initializes random. In maybe 1 in 20 calls to this function I, for whatever, reason unwrap a nil value for playlist.items.count and can't seem to figure out why. Maybe it's something I don't understand about API calls or something else I'm failing to see but I can't seem to make sense of it.
Anyone have any recommendations on addressing this issue or how to go about debugging this?
Ok, after sleeping on it and working on it some more I seem to have resolved the issue. Here's the error handling I implemented into my code.
if let actualPlaylist = playlist, actualItems = actualPlaylist.items {
if actualItems.count == 0 {
SongScraper.playlistHasSongs = false
print("Empty playlist, loading another playlist")
return
}
for _ in 1...numberOfSongs {
let random = Int(arc4random_uniform(UInt32(actualItems.count)))
songs.append(actualPlaylist.items[random] as! SPTPartialTrack)
}
completionHandler(songs: songs)
}
else {
print("Returned a nil playlist, loading another playlist")
SongScraper.playlistHasSongs = false
return
}

AppleWatch Messages URL works hard coded but not with variables

TLDR When I hard code phone numbers into a URL it opens in watch messages correctly, but when I use a variable string with the numbers typed in exactly the same way inside of it, it doesn't.
Example:
NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=Test")
Above code works but below code doesn't:
let hardCode = "8888888888,9999999999,3333333333"
NSURL(string: "sms:/open?addresses=\(hardCode)&body=Test")
FULL DETAILS:
I am making a URL from variables to open messages on the Apple Watch with pre-filled contents. I am getting the phone numbers from the contact book and storing them in an array. They are provided in this format:
(###) ###-#### but need to be ##########
I tested the code by hard-coding phone numbers into the URL and it works properly with all contacts and completed body:
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=8888888888,9999999999,3333333333&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
But when I build the phone number values programmatically it does not work:
//holds phone numbers without special chars
var tempArray: [String] = []
//if I can access the unformatted numbers
if let recips = saveData["recips"] as? [String] {
//for each number provided
recips.forEach { (person: String) in
//remove all non-numerical digits
//person is now (###) ###-####
let newPerson = person.digitsOnly()
//newPerson is ##########
print(person)
print("->\(newPerson)")
//add formatted number to tempArray
tempArray.append(newPerson)
}
}
//combine all numbers with "," between as a string
let recipString = tempArray.joinWithSeparator(",")
//recipString contains ##########,##########,##########...
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
I then add the "recipString" variable to the NSURL in the below code:
let messageBody = "test"
let urlSafeBody = messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(recipString)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
The FINAL URL print shows the correct string, but the messages app does not open properly, and shows quick reply menu instead of composed message window. It matches the functioning hard coded number version exactly, but behaves differently.
Totally lost, hope someone can help!
UPDATE 1
Here are the debug prints for both versions of the URL:
Manually declared (not created from recipString but actually declared in the URL string explicitly):
This version works
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
Variable created (using recipString):
This version doesn't
FINAL URL: sms:/open?addresses=0000000000,1111111111,2222222222,3333333333,4444444444&body=test
I have also tried applying url encoding to the "recipString" variable by using the below if let:
if let urlSafeRecip = recipString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
if let urlSafeBody = urlSafeBody, url = NSURL(string: "sms:/open?addresses=\(urlSafeRecip)&body=\(urlSafeBody)") {
print("FINAL URL: \(url)")
WKExtension.sharedExtension().openSystemURL(url)
}
}
UPDATE 2
I tested to see if the hardcode version of numbers matches the recipString exactly via this code:
let hardCode = "0000000000,1111111111,2222222222,3333333333,4444444444"
let isEqual = (hardCode == recipString)
if isEqual {
print("hardCode matches recipString")
}
else {
print("hardCode does not match recipString")
}
Debug prints:
hardCode matches recipString
UPDATE 3
I have confirmed that:
When a URL is made with hard coded numbers vs. numbers that I make from variables, checking == between them returns true.
In every test I can do between the two version of the url, it matches.
NOTES AFTER CORRECT ANSWER FOUND:
This type of URL formatting will ONLY work with multiple addresses in the URL. If you do not have multiple addresses you will need to do the following, which is undocumented but none-the-less works. I found this by bashing my face on the keyboard for hours, so if it helps you an upvote is deserved :)
follow the answer marked below, and then use this type of logic check before making the URL in the doItButton() function he mentioned:
func setupAndSendMsg(saveData: NSDictionary) {
if let urlSafeBody = createBody(saveData) {
let theNumbers = createNumbers(saveData).componentsSeparatedByString(",")
print(theNumbers.count-1)
if theNumbers.count-1 > 0 {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers(saveData))&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
} else {
if let url = NSURL(string: "sms:/open?address=\(createNumbers(saveData)),&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
}
My guess is that it is not the acctual openSystemUrl call that is the problem. I believe there must be something with the code that is building the number string programmatically.
The code bellow is a simplified version of all the code you have posted. I have confirmed that it is working on my Apple Watch. It opens the Messages app with pre-populated numbers & body text.
Take one more look at your code and see if there is something your missing. If you can't find anything, just delete the code and re-write it, probably will be faster then spotting the weird issue.
Once again the code bellow is confirmed working as expected, so you should be able to get it to work. (or just copy & paste my code) :)
class InterfaceController: WKInterfaceController {
#IBAction func doItButton() {
if let urlSafeBody = createBody() {
if let url = NSURL(string: "sms:/open?addresses=\(createNumbers())&body=\(urlSafeBody)") {
print(url)
WKExtension.sharedExtension().openSystemURL(url)
}
}
}
private func createBody() -> String? {
let messageBody = "hello test message"
return messageBody.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLHostAllowedCharacterSet())
}
private func createNumbers() -> String {
let numbers = ["(111) 222-3333", "(444) 555-6666"]
var tempArray: [String] = []
numbers.forEach { (number: String) in
tempArray.append(number.digitsOnly())
}
return tempArray.joinWithSeparator(",")
}
}
extension String {
func digitsOnly() -> String{
let stringArray = self.componentsSeparatedByCharactersInSet(
NSCharacterSet.decimalDigitCharacterSet().invertedSet)
let newString = stringArray.joinWithSeparator("")
return newString
}
}
With above said I would recommend against using undocumented Apple features for anything you plan on putting on the App Store for the reasons already mentioned in comments.