Open links in textview - xcode

I am coding on a project were the user presses a button and a in the textview(named textoutput) under the button the program randomly takes a link from a array i have named textinfo. When running the project the first time you press the the button and presses the link the right link opens up in safari but the second time the button is pressed and a new randomly selected link is shown and when pressing the link it opens the first link and not the one pressed. This continues every time when pressing the button that just the first link is open when pressing a new link.
#IBAction func Frukostaction(sender: UIButton) {
let random = Int(arc4random_uniform(19))
textoutput.text = textinfo[random]
}
How do a fix this? Thanks in advance.

I solved this problem by setting the UITextView.delegate and then calling openURL manually inside shouldInteractWithURL function. For example.
let links = ["http://www.google.com","http://www.yahoo.com","http://www.discovery.com"]
var textView : UITextView!
var currentLinkIndex : Int = 0
#IBAction func buttonClick(sender: AnyObject) {
let random : Int = Int(arc4random()) % 3
textView.text = links[random]
currentLinkIndex = random
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
let url = NSURL(string:links[currentLinkIndex])
UIApplication.sharedApplication().openURL(url!)
return false
}

Related

SwiftUI Update the mainMenu [SOLVED] kludgey

The Real Question
How do you update the mainMenu in SwiftUI so that it actually works?
I have built a MacOS Document Based application in SwiftUI which includes all of the in-built File menu commands (i.e. Close, Save, Duplicate. Rename... etc.)
Before saving the document, I validate the structure and would like to present a modal dialog to the user if there are any validation errors.
The modal dialog is just a simple OK/Cancel dialog - 'OK' meaning that the user is happy to save the file with validation errors, 'Cancel' would need to stop the save operation.
So the question is: "How do I intercept the in-built 'Save' menu command to present this dialog?
I have tried to overwrite the .saveItem CommandGroup - but this replaces all of the menu items and I only want to override a couple of the commands ('Save' and 'Save As') and don't want to re-implement them all (and I am not sure that I have the skills to do so)
.commands {
CommandGroup(replacing: .saveItem) {
// code goes here - but removes all of the in-built menus
}
}
I have tried this solution (In a SwiftUI Document App, how to save a document from within a function)
and have put it into my AppDelegate
public func applicationDidBecomeActive(_ notification: Notification) {
let menu = NSApplication.shared.mainMenu!.items.first(where: { $0.title == "File" })!
let submenu = menu.submenu!.items.first(where: { $0.title == "Save" })!
submenu.action = #selector(showDialog)
}
#objc func showDialog() {
var retVal: Int = 0
let thisWindow: NSWindow? = NSApplication.shared.mainWindow
let nsAlert: NSAlert = NSAlert()
let cancelButton: NSButton = nsAlert.addButton(withTitle: "Cancel")
cancelButton.tag = 1
let okButton: NSButton = nsAlert.addButton(withTitle: "OK")
okButton.tag = 0
// The below code is replaced
nsAlert.beginSheetModal(for: thisWindow!) { modalResponse in
print(modalResponse)
retVal = modalResponse.rawValue
if retVal == 0 {
print("save")
} else {
print("cancel")
}
}
}
However it doesn't actually call the showDialog function.
Edit/Update
I am still having difficulties updating the menus, but in the above example the call to beginModalSheet is incorrect as the process will run in the background. Updated the call to runModal() which will stop any background process writing the file.
#objc func showDialog() {
let nsAlert: NSAlert = NSAlert()
let cancelButton: NSButton = nsAlert.addButton(withTitle: "Cancel")
cancelButton.tag = 1
let okButton: NSButton = nsAlert.addButton(withTitle: "OK")
okButton.tag = 0
let response: Int = nsAlert.runModal().rawValue
if response == 0 {
print("save")
NSApp.sendAction(#selector(NSDocument.save(_:)), to: nil, from: nil)
} else {
print("cancel")
}
}
I have read somewhere that you need to set the menu before the window appears, and I have also read that you need to set the menus before the AppDelegate is set.
Yet another edit
See this post Hiding Edit Menu of a SwiftUI / MacOS app
and this comment
Thoughts: SwiftUI either has a bug or they really don't want you to remove the top level menus in NSApp.mainMenu. SwiftUI seems to reset the whole menu with no way to override or customize most details currently (Xcode 13.4.1). The CommandGroup(replacing: .textEditing) { }-esque commands don't let you remove or clear a whole menu. Assigning a new NSApp.mainMenu just gets clobbered when SwiftUI wants even if you specify no commands.
XCode 14.1
Swift 5
After a lot of super frustrating searching an attempts and lots of code - I reduced the problem to being just trying to change the name of the save menu item - If I could do this - then I can change the action for it as well.
Here is how I did it
My Tetsing App is called YikesRedux
Steps:
Register the AppDelegate
Override the applicationWillUpdate method
Put the menu updating in a DispatchQueue.main.async closure
Cry tears of joy that you have solved this problem after days of searching
YikesAppRedux.swift
import SwiftUI
#main
struct YikesReduxApp: App {
#NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate // <- Don't forget the AppDelegate
var body: some Scene {
DocumentGroup(newDocument: YikesReduxDocument()) { file in
ContentView(document: file.$document)
}
}
}
AppDelegate.swift
import Foundation
import AppKit
public class AppDelegate: NSObject, NSApplicationDelegate {
public func applicationWillUpdate(_ notification: Notification) {
DispatchQueue.main.async {
let currentMainMenu = NSApplication.shared.mainMenu
let fileMenu: NSMenuItem? = currentMainMenu?.item(withTitle: "File")
if nil != fileMenu {
let saveMenu = fileMenu?.submenu!.item(withTitle: "Save")
if nil != saveMenu {
print("updated menu")
saveMenu?.title = "Save Updated"
}
}
}
}
}
I put this down as a bit kludgey - as it runs on every application update (which is not a lot, but you can see the print out in the console "updated menu" when it does occur)
I did try to keep a state variable as to whether the menu was updated, to try and not do it again - but in a multi document window environment you would need to keep track of every window... (Also swift just clobbers the menu whenever it wants - so it didn't work as well as expected.)
I put the menu updating code in almost everywhere I could think of
Every single AppDelegate function override
init methods for the App, the ContentView
on the document read function/write function
You name it - I put it in there (I even had a hosting controller, a NSViewRepresentable)
I then removed them one by one until I found the solution.
I would be happy if there was a less kludgey way to do this.

Window close and restore

I'm creating an app for Mac OSX, (I don't know anything about SwiftUI/Cocoa) this app needs to be hidden when the user presses one button and at a certain time the app should pop-up again.
It's a reminder app:
#IBAction func hidMe(_ sender: NSButton) {
window.close() //I know this will remove the window, but I didn't find another way...
}
//I created this function that was supposed to bring the window to front, but I have no idea how to call it.
func reopen() {
let now = Date()
let components = Calendar.current.dateComponents([.month, .day, .hour, .minute], from: now)
if ((components.hour == 9) && (components.minute == 16)) {
window?.makeKeyAndOrderFront(self)
}

UIPicker - how record the standard selectedRow if user only click the OK button

I have a UIpicker in modal window. Everything works fine, except one thing. When the user click the input text field, the modal window with the UIpickerView opens as expected. If the selected row is the correct one and the user click the OK button, then the current code is not able to registrer the value.
// Set the default row, so if user click ok without changing it is recorded
self.picker.selectRow(0, inComponent: 0, animated: false)
Is there an alternative to didSelectRow? I assume that is the issue, as they user don't select a row as it is already preselected by the code.
// Capture the picker selection
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
// This method is triggered whenever the user makes a change to the picker selection.
// The parameter named row and component represents what was selected.
selectedType = pickerData[row] as String
}
Well solved it. just add a test to see if the selectedType was nil.
#IBAction func OkBtnWasPressed(_ sender: Any) {
// update text to selected value
if selectedType == nil {
typeTextField.text = pickerData[defaultRow]
} else {
typeTextField.text = selectedType
}
animateOut()
}

how to change the button identifier when click it in xcode

I'm new to Xcode and I'm using Swift to program a stopwatch.
I have three functionalities - Play, Stop & Pause. Play and Pause should be the same button and alternate between states. How do I accomplish this in code?
You can user the property selected of button
like this :
setup your button states in viewDidLoad:
startButton.setTitle("Stop", forState: UIControlState.Selected)
startButton.setTitle("start", forState: UIControlState.Normal)
Now you can change the button states when it's tapped:
#IBAction func startPauseAction(sender: UIButton) {
sender.selected = !sender.selected // action changed the selected
if sender.selected {
play()
} else {
pause()
}
}
Using an enum for button state
enum ButtonType {
case Stop
case Play
case Pause
}
var btnType: ButtonType = .Stop
#IBOutlet weak var actionBtn: UIButton!
#IBAction func buttonClicked(sender: UIButton) {
switch (self.btnType) {
case .Stop:
self.btnType = .Play
// TODO: Change button image/title for current state
break
case .Play:
self.btnType = .Pause
// TODO:
break
case .Pause:
// TODO:
break
}
}

Xcode Swift: How to stop variables from changing back after each viewDidLoad, how to save and update data from different ViewControllers?

I have some vars in my Main VC and when user clicks a button in another VC the prepareForSegue passes along a new value to the Main VC and updates a label.
But when the user clicks again it's back to initial value, so it doesn't increment since the value is set back in the viewDidLoad?
MainVC:
var statsHealth:Int = 0
override func viewDidLoad() {
super.viewDidLoad()
healthLabel.text = String("Health: \(statsHealth)/10")
}
Another VC:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "startSegue") {
let startVC = segue.destinationViewController as ViewController
startVC.statsHealth += 1
}
It's displayed as 0, then 1 but then 0 again and then 1 instead of 2,3,4 etc.
Any ideas?
BR
Nils
Perhaps not the most 'Swift' way to do it, but certainly works well....
Create a file called Variables.swift which will hold all your 'universal' variables (if these are going to be on every page, I see no reason this isn't the 'best' way to do it - certainly it is the most simple to understand!)
in Variables.swift, hold all your universal variables
struct Variables {
static var statsHealth = 0
.....
}
Then, in each other page, access them at any time
healthLabel.text = String("Health: \(Variables.statsHealth)/10")
or set them
Variables.statsHealth += 1
So based on your description, I assume the view controller structure is like this:
AnotherVC -> MainVC
MainVC is presented on top of AnotherVC. When you go back to AnotherVC, did you dismiss MainVC completely? If so, then every time you go from AnotherVC to MainVC, it initiate a new ViewController, and the variables you saved before doesn't exist anymore.
If you want to keep this structure and change variables in MainVC, keep a reference of mainVC in AnotherVC. Then instead of connecting in storyboard, you may want to present it programmatically.
class AnotherVC {
var mainVC: MainVC?
func presentMainVC() {
var targetVC = UIViewController()
if self.mainVC != nil {
targetVC = self.mainVC
} else {
let storyboard = UIStoryboard(name: "Your-storyboard-name", bundle: nil)
targetVC: MainVC = storyboard.instantiateViewControllerWithIdentifier("The-main-VC-identifier") as MainVC
self.mainVC = targetVC
}
//you can change your variable here
mainVC.statsHealth += 1
self.presentViewController(self.mainVC, animated: true, completion: nil)
}
If you mainVC is on top of AnotherVC in any case, you can just revert the reference direction.

Resources