I have a MacOS app that is a registered custom URL handler.
I'm trying to make it show a specific NSViewController if the app is started via the url handler or the regular window and ViewController if no parameters are used.
In my AppDelegate.swift I have this:
func application(_ application: NSApplication, open urls: [URL]) {
AppDelegate.externalCaller = true;
NSApp.hide(self)
let url: URL = urls[0];
// Process the URL.
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true);
let method = components?.host;
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if (method == "DO_STH") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "DO_STH_2") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "PROCESS_STUFF") {
// Show window
DispatchQueue.main.async {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let restoringViewController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("restoringData")) as! RestoringViewController;
if let window = NSApp.mainWindow {
window.contentViewController = restoringViewController;
}
NSApp.activate(ignoringOtherApps: true);
AppDelegate.restoreData();
}
}
}
}
}
The problem is that by the time NSApp.hide(self) runs, there's already a window visible with the default viewController, creating some flickering effect.
What's the best way to make the app start without any visible window by default and only show the window if it wasn't started with any URL parameter and later on demand if a specific URL parameter exists?
Unchecking "Is initial Controller" and adding this to AppDelegate solved my issue
func applicationDidFinishLaunching(_ notification: Notification) {
if (!AppDelegate.externalCaller) {
showWindow();
}
}
func showWindow() {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let windowController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MainWindowController")) as! NSWindowController;
windowController.showWindow(self);
}
Related
First of all, I have never used notifications in an app. I have done tutorials but the whole thing confuses me.
I have created a SwiftUI file called Notify.swift. I want the user to be able to set a time for a notification to alert them to perform a task at a specified time, like in this image:
Where you see the time in the image, I have created a DatePicker to pick a time for the notification:
VStack {
Button(action: {}) {
HStack {
DatePicker(" Select a time ....",
selection: $wakeup, displayedComponents: .hourAndMinute)
.font(.title2)
.accentColor(Color(.white))
}
}.background(Color(.black))
}
.frame(width: .infinity, height: 40, alignment: .center)
.padding()
When the user clicks on the Create Button to set the notification, it should set the notification at that particular time (all the time, unless changed). This is what I need to happen but don't know how to do it:
If the notification time is set for 8:30am, like in the image, and the user selects CREATE, a notification is set and should be sent to the user to perform whatever task with maybe a sound and a message at that specified time.
I understand that there are different types of notification: local, user, Apple push, etc, but I don't know which type this falls in or how to do it.
Would this be a notification or an alarm?
You can use local notifications for that. Here I made a function for you to trigger the notification. First off all, check if that time is prior the current time. Then the notification will be tomorrow and we add one day to our Date. You can change title, body as you wish.
Make sure to wrap your DatePicker outside the button, otherwise it will always trigger a notification when you click the DatePicker.
func scheduleNotification() -> Void {
let content = UNMutableNotificationContent()
content.title = "Your title"
content.body = "Your body"
var reminderDate = wakeup
if reminderDate < Date() {
if let addedValue = Calendar.current.date(byAdding: .day, value: 1, to: reminderDate) {
reminderDate = addedValue
}
}
let comps = Calendar.current.dateComponents([.year, .month, .day, .hour, .minute], from: reminderDate)
let trigger = UNCalendarNotificationTrigger(dateMatching: comps, repeats: false)
let request = UNNotificationRequest(identifier: "alertNotificationUnique", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) {(error) in
if let error = error {
print("Uh oh! We had an error: \(error)")
}
}
}
Also you need to request permission for Notifications like this:
func requestPush() -> Void {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
if success {
print("All set!")
} else if let error = error {
print(error.localizedDescription)
}
}
}
Here your button:
VStack {
Button(action: {
scheduleNotification()
}) {
Text("Save notification")
}
DatePicker("Select a time ....",
selection: $wakeup, displayedComponents: .hourAndMinute)
.font(.title2)
.accentColor(Color(.white))
}
Scenario: TabbedUI with Tab #1 being a WkWebView displaying a PDF of general information (Info).
Problem: When I exit tab #1 (info) for another tab (ex. search), and return....I get an empty PDF with the following error message in the console:
Could not signal service com.apple.WebKit.WebContent: 113: Could not
find specified service
However this doesn't happen when I use a standard .rtf (Rich Text Format) file.
Here's my code:
import SwiftUI
import WebKit
struct IntroSwiftUI: View {
var body: some View {
Webview()
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
.edgesIgnoringSafeArea(.bottom)
}
}
// =====================================================================================================
struct Webview: UIViewRepresentable {
func makeUIView(context: Context) -> WKWebView {
guard let url = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") else {
print("Inside Webview: unable to reload ReadMe.")
return WKWebView()
}
let request = URLRequest(url: url)
let wkWebview = WKWebView()
wkWebview.load(request)
wkWebview.scrollView.bounces = false
return wkWebview
}
func updateUIView(_ uiView: WKWebView, context: Context) {
}
}
I just want a static PDF to be available for viewing whenever the user which to read some documentation (with embedded images). But apparently it tries to rebuild and gets lost.
Do I need to do something 'special' with the PDF, like release it when exiting & re-create it upon return? That seems totally inefficient.
Per suggestion,
Use the PDFView paradigm.
Here is my revised (correct) code for PDF viewing:
import SwiftUI
import UIKit
import PDFKit
struct PDFKitRepresentedView: UIViewRepresentable {
let url: URL
init(_ url: URL) {
self.url = url
}
func makeUIView(context: UIViewRepresentableContext<PDFKitRepresentedView>) -> PDFKitRepresentedView.UIViewType {
// Create a `PDFView` and set its `PDFDocument`.
let pdfView = PDFView()
pdfView.document = PDFDocument(url: self.url)
return pdfView
}
func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<PDFKitRepresentedView>) {
// Update the view.
}
}
struct PDFKitView: View {
var url: URL
var body: some View {
PDFKitRepresentedView(url)
}
}
// =====================================================================================================
struct IntroSwiftUI: View {
var body: some View {
if let documentURL = Bundle.main.url(forResource: "ReadMe", withExtension: "pdf") {
VStack(alignment: .center) {
Text("Introduction")
.font(.title3)
.fontWeight(Font.Weight.medium)
PDFKitView(url: documentURL)
}
} else {
Text("Sorry, No PDF")
}
}
}
I've been playing around with Turbolinks 5 and I can't seem to get it to visit a new page correctly after clicking a link within my application. The app loads the new view as if it was replaced inside the webview and doesn't push a new view controller on to the stack like I would expect. It's as if it doesn't perform a Turblonks.visit
I'm running a rails 5.1 application with the Turbolinks 5 enabled. My link looks like this:
<%= link_to 'View', test_path(test_id), class: 'btn btn-secondary btn-block marginTop_short' %>
As you can see there is nothing special about this link!
My iOS app code is very basic:
import UIKit
import Turbolinks
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigationController = UINavigationController()
var session = Session()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window?.rootViewController = navigationController
startApplication()
return true
}
func startApplication() {
session.delegate = self
visit(URL: NSURL(string: "http://localhost:3000")!)
}
func visit(URL: NSURL) {
print("visiting", URL)
let visitableViewController = VisitableViewController(url: URL as URL)
navigationController.pushViewController(visitableViewController, animated: true)
session.visit(visitableViewController)
}
}
extension AppDelegate: SessionDelegate {
func session(_ session: Session, didProposeVisitToURL URL: URL, withAction action: Action) {
print("trying to visit", URL)
print("action", action)
visit(URL: URL as NSURL)
}
func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, withError error: NSError) {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
navigationController.present(alert, animated: true, completion: nil)
}
}
When I click a link it doesn't fire the func session(_ session: Session, didProposeVisitToURL URL: URL, withAction action: Action) callback.
Maybe it's more accurate to say when a link is clicked the webview is not responding to or createing a visit proposal?
What am I missing? Any help would be appreciated.
Let me know if any more detail is require or clarification.
My code is trying to download some JSON data and save it to an array, then loop through the array and create a button for each item. I am having trouble assigning my function to the buttons for giving them functionality. Here is my code:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//connect to website
let SongArray: Array<Any>
let url = URL(string:"*******")
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil
{
print("error")
}
else
{
if let content = data
{
do
{
//download JSON data from php page, display data
let SongArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as! [[String]]
print(SongArray)
//Make buttons with JSON array
var buttonY: CGFloat = 20
for song in SongArray {
let SongButton = UIButton(frame: CGRect(x: 50, y: buttonY, width: 250, height: 30))
buttonY = buttonY + 50 // 50px spacing
SongButton.layer.cornerRadius = 10 //Edge formatting for buttons
SongButton.backgroundColor = UIColor.darkGray //Color for buttons
SongButton.setTitle("\(song[0])", for: UIControlState.normal) //button title
SongButton.titleLabel?.text = "\(song[0])"
SongButton.addTarget(self,action: #selector(songButtonPressed(_:)), for: UIControlEvents.touchUpInside) //button press / response
self.view.addSubview(SongButton) // adds buttons to view
}
}
catch
{
}
}
}
}
task.resume()
}
} //close viewDidLoad
func songButtonPressed(_sender:UIButton!) { // function for buttons
if sender.titleLabel?.text == "\("Song[0]")" {
print("So far so good!!")
}
}
I am getting an error on the line with SongButton.addTarget...
the error says 'Use of Unresolved Identifier "SongButtonPressed"' even though its declared right after the viewDidLoad function.
Since you declared the selector SongButtonPressed(_:) it's (note the underscore)
func SongButtonPressed(_ sender: UIButton) { // no implicit unwrapped optional !!
By the way the proper selector syntax is #selector(SongButtonPressed(_:))
Two notes:
.mutableContainers has no effect in Swift at all. Omit the parameter. And delete the line let SongArray: Array<Any>. You should get an unused warning.
Functions, methods and variables are supposed to start with a lowercase letter.
I am trying to implement the action from notification. And so far I am able to trigger the right delegate functions, but after tapping the app is not brought to the foreground.
Relevant code:
#available(iOS 10.0, *)
func registerCategory() -> Void{
print("register category")
let callNow = UNNotificationAction(identifier: "call", title: "Call now", options: [])
let clear = UNNotificationAction(identifier: "clear", title: "Clear", options: [])
let category : UNNotificationCategory = UNNotificationCategory.init(identifier: "IDENT123", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}
#available(iOS 10.0, *)
func scheduleNotification(event : String, interval: NSTimeInterval) {
print("schedule ", event)
let content = UNMutableNotificationContent()
content.title = event
content.body = "body"
content.categoryIdentifier = "CALLINNOTIFICATION"
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: interval, repeats: false)
let identifier = "id_"+event
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.currentNotificationCenter()
center.addNotificationRequest(request) { (error) in
}
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
print("willPresent")
completionHandler([.Badge, .Alert, .Sound])
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
let notification: UNNotification = response.notification
let UUID = notification.request.content.userInfo["UUID"] as! String
switch (response.actionIdentifier) {
case "COMPLETE":
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
case "CALLIN":
let call = Call()
CalendarController.sharedInstance.fetchMeetingByUUID(UUID, completion: { (thisMeeting) -> Void in
if(!CallIn.Yield(thisMeeting).ConferenceCallNumber.containsString("None")){
call._call(thisMeeting)
}else{
//will open detail view, in case that no number were detected
NSNotificationCenter.defaultCenter().postNotificationName("OpenDetailViewOfMeeting", object: self, userInfo: ["UUID":UUID])
}
})
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
default: // switch statements must be exhaustive - this condition should never be met
log.error("Error: unexpected notification action identifier: \(UUID)")
}
completionHandler()
}
I am able to hit the delegate function didReceiveNotificationResponse() with a breakpoint, and it does some actions that I put there, but not in a way that is expected (It has to start a device-call, instead it just dismisses notifications list, and nothing happens, however when I manually open the app again, the call starts as if there is no permission to open the app from notification).
I found out the reason myself, so this might be helpful to someone in the future. The answer turned out to be quite simple. When creating an action of the notification, there is this parameter: options. When you register category, you need to put it either way .Foreground or .Destructive like this:
func reisterCategory () {
let callNow = UNNotificationAction(identifier: NotificationActions.callNow.rawValue, title: "Call now", options: UNNotificationActionOptions.Foreground)
let clear = UNNotificationAction(identifier: NotificationActions.clear.rawValue, title: "Clear", options: UNNotificationActionOptions.Destructive)
let category = UNNotificationCategory.init(identifier: "NOTIFICATION", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}