Cocoa - PDFView/ThumbnailView dragging - swift

I have a PDFView and a PDFThumbnailView and implemented it this way:
class PDFViewController: NSViewController {
#IBOutlet weak var pdfView: PDFView!
#IBOutlet weak var pdfThumbnailView: PDFThumbnailView!
override func viewDidLoad() {
super.viewDidLoad()
// Do view setup here.
let doc = PDFDocument(data: NSData(contentsOfFile: "file.pdf")!)
pdfView.setDocument(doc)
pdfView.setAllowsDragging(true)
pdfThumbnailView.setPDFView(pdfView)
pdfThumbnailView.setAllowsDragging(true)
}
}
Since the documentation says:
Sets whether users can drag thumbnails within the thumbnail view; that
is, re-order pages in the document.
I thought this would do it, but it isn't doing anything. I can't drag and drop and so reorder the document.

Connect pdfThumbnailView and pdfView before setting the document, in the xib or in code.

Related

Enabling and disabling buttons in xcode

For a given simple audio app with a few buttons:
The button references inside ViewController is:
#IBOutlet weak var recordAudioButton: UIButton!
#IBOutlet weak var playAudioButton: UIButton!
#IBOutlet weak var processAudioButton: UIButton!
But where are those button names and references inside the xcode gui? Notice below that the Allow Recording button is highlighted: but there is no mention of recordAudioButton as a button name:
I want to modify the enabling/disabling logic of a different button that does not have a reference yet.. but can not see how/where to do that . The dialog does not show a way to view/change the button references. So where is the place to do that?
See in Referencing Outlets Section for each button.
You can set disable after buttons setup
#IBOutlet weak var recordAudioButton: UIButton! {
didSet { recordAudioButton.isEnabled = false }
}
#IBOutlet weak var playAudioButton: UIButton! {
didSet { playAudioButton.isEnabled = false }
}
#IBOutlet weak var processAudioButton: UIButton! {
didSet { processAudioButton.isEnabled = false }
}
Then in viewDidLoad check for permission
AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
DispatchQueue.main.async {
self.recordAudioButton.isEnabled = allowed
self.playAudioButton.isEnabled = allowed
self.processAudioButton.isEnabled = allowed
}
}
Then your code
#IBAction func askForPermissions() {
recordingSession = AVAudioSession.sharedInstance()
do {
try recordingSession.setCategory(.playAndRecord, mode: .default)
try recordingSession.setActive(true)
recordingSession.requestRecordPermission() { [unowned self] allowed in
// UI related work has to be executed on main thread(queue)
DispatchQueue.main.async {
self.recordAudioButton.isEnabled = allowed
self.playAudioButton.isEnabled = allowed
self.processAudioButton.isEnabled = allowed
}
}
} catch let error {
presentError(withMessage: error.localizedDescription)
}
}
You just defined the button as an outlet. So it does not appear on the Touch Up Inside, it appears at outlets area. But you connect your other button to an action function, this button appears on the Touch Up Inside.
Given the starting point / hint from #Picode here is what I was missing to get a new UIButton reference. A Medium article helped pave the way https://medium.com/#GanChau/outlet-vs-action-connections-in-xcode-b5331fb233a1 . In particular we need to have both an Editor and the Visual Designer showing:
So on my project now I control-clicked on the new button Run DSP and the context menu shows up:
Now select New Referencing Outlet and connect it to the ViewController code:
I typed in dspJsButton for the Name and we get the following code generated:
#IBOutlet weak var dspJsButton: UIButton!

Loading GIFs using SDWebImage is taking up far more memory than anticipated

I am building a UITableView populated with images and GIFs downloaded using SDWebImage. The images are shown in the table cells, whereas the GIF are shown as a 3D touch preview.
The problem is that every time a GIF is previewed the memory spikes (as expected) however the memory use increases far more than I thought it would. Downloading one of the GIFs manually, I found that it's only around 500kB, but when doing it in the app through SDWebImage, sethe memory increase is closer to 100MB.
After just a few GIF previews the app crashes due to memory issues...
At first I thought perhaps the instance of GIFView was not being deallocated when the GIF preview was exited - I checked with '''deinit''' and it does seem to be deallocating.
I also reset the GIFView to '''nil''' in viewDidDisappear but that did not help either.
Looking at the Allocations Instrument, I found that the GIFs are being permanently stored in memory when they are first loaded. This explains why viewing the same GIF again does not increase the memory.
Does anyone see why these GIFs are taking so much memory? I also built a version of the app where all the assets were local and that ran smoothly with no hiccups or memory issues - but surely the GIF sizes should be the same size in both cases?!
If there is no leak and these memory spikes are inevitable, is there any other way of clearing the GIF from memory when it disappears?
Code is below
//model
struct Visualisation: Codable {
var id: Int
let name, info, url_name, tags, imageURL, gifURL: String
}
//3D touch GIF preview
extension ViewController: UIViewControllerPreviewingDelegate {
func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = tableView.indexPathForRow(at: location) else { return nil }
let selectedVis = filteredVisualisations[indexPath.row]
let identifier = "GIFViewController"
guard let GIFVC = storyboard?.instantiateViewController(withIdentifier: identifier) as? GIFViewController else { return nil}
GIFVC.selection = selectedVis
GIFVC.preferredContentSize = CGSize(width: 0, height: 190)
return GIFVC
}
//GIFViewController
import UIKit
import SDWebImage
class GIFViewController: UIViewController {
#IBOutlet weak var GIFView: UIImageView!
var selection = visualisations[0]
override func viewDidLoad() {
super.viewDidLoad()
GIFView.sd_setImage(with: URL(string: selection.gifURL))
// Do any additional setup after loading the view.
}
override func viewDidDisappear(_ animated: Bool = true) {
}
deinit {
print("DEALLOCATED GIFViewController class")
}
}
//setting the table cells
class TableViewCell: UITableViewCell {
#IBOutlet weak var ImageView: UIImageView!
#IBOutlet weak var TitleLabel: UILabel!
#IBOutlet weak var InfoLabel: UILabel!
func setCell(visualisation: Visualisation){
ImageView.sd_setImage(with: URL(string: visualisation.imageURL))
TitleLabel.text = visualisation.name
InfoLabel.text = visualisation.info
}
}
Any help would be much appreciated.
P.S. I am sure you've already noticed that I am very new to iOS development... Pardon my 'newbieness'...

WKWebView shows a blank screen on OSX with no output

For some reason, the WKWebView just shows a blank screen with no errors
import Cocoa
import WebKit
class ViewController: NSViewController, WKNavigationDelegate {
#IBOutlet weak var webView: WKWebView?
override func viewDidLoad() {
super.viewDidLoad()
webView?.navigationDelegate = self
let url=URL(string: "https://www.google.com")
webView?.load(URLRequest(url: url!))
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Like I said, no errors, and it just shows a blank screen.
The storyboard looks like this:
Main.storyboard
You need to allow your application to have outgoing connections.
In your Target go to Capabilities, enable the App SandBox and check the Outgoing Connections.

change to another screen after login is successful

The use case of the app I am developing is I will have login view at the initial. When user logs in with valid credentials, the user should see another view saying welcome user. I am totally beginner and I don't know much more about xcode. I see the screen navigation from storyboard but I have already done many things in xib.
Here is what I have done
SafariExtensionViewController.swift
import SafariServices
class SafariExtensionViewController: SFSafariExtensionViewController {
#IBOutlet weak var passwordMessage: NSTextField!
#IBOutlet weak var emailMessage: NSTextField!
#IBOutlet weak var message: NSTextField!
#IBOutlet weak var email: NSTextField!
#IBOutlet weak var password: NSSecureTextField!
static let shared = SafariExtensionViewController()
override func viewDidLoad() {
self.preferredContentSize = NSSize(width: 300, height: 250)
message.stringValue = ""
emailMessage.stringValue = ""
passwordMessage.stringValue = ""
}
#IBAction func userLogin(_ sender: Any) {
let providedEmailAddress = email.stringValue
let providedPassword = password.stringValue
let isEmailAddressValid = isValidEmailAddress(emailAddressString: providedEmailAddress)
self.message.stringValue = ""
emailMessage.stringValue = ""
passwordMessage.stringValue = ""
if isEmailAddressValid && providedPassword.count > 0 {
// api call is done here
// when success should show another screen
} else {
emailMessage.textColor = NSColor.red
emailMessage.stringValue = "Invalid Email"
}
}
}
here is the screenshot of xib.
Technology used
swift 4
xcode 9
not IOS its mainly for app extension
UPDATE
I am not using storyboard and also it's not IOS. I am using xib and from macos project I am trying to develop app extension which will be shown in browser as an extension.
A lot of time left from question posted, but it can be helpful for others as me (I'm porting safari legacy extension to safari app extension) and find the way with use of NSTabView with tabless style:
The use is:
Ensure to add NSObject in your storyboard from the Library:
And connect with the tabview from Outlet:
I'm rather newbie in Cocoa and Swift and if anyone knows more beautiful solution for routing, please share it with others!

Cannot form weak reference to instance of class NSTextView

Using Swift only, here's my code in AppDelegate.swift:
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
#IBOutlet var textField: NSTextView
#IBAction func displaySomeText(AnyObject) {
textField.insertText("A string...")
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
In the interface builder, I have an object hooked up to receive input from a button, then the output goes to a text view. I'm trying to get the text view to populate with some text when I hit the button.
I tried this with a text field as well, and didn't get the error, but got a "dong" error sound and it didn't do anything else. In Objective-C, you had to use the (assign) parameter to get this to work from what I understand.
What am I doing wrong?
You cannot store a weak reference to an NSTextView due to historical issues with Cocoa and AppKit. See details in the Clang documentation. NSTextView is marked as NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE in NSTextView.h, there are also a few other classes to lookout.
Have you tried a Swift unowned reference instead of weak, which is kind of like Objective-C's assign (what you'd use for an NSTextView outlet in Objective-C)?
Use #IBOutlet var scrollView: NSScrollView instead of #IBOutlet var textField: NSTextView.
Then create a property returns documentView in scrollView.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow
#IBOutlet var scrollView: NSScrollView
var textField: NSTextView {
get {
return scrollView.contentView.documentView as NSTextView
}
}
#IBAction func displaySomeText(AnyObject) {
textField.insertText("A string...")
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
I have tried to replicate what you described. I have created a new OS X app using Xcode 6 beta 7. I have dropped a button and text view in the main form.
I think your problem is that the connection to the Text View object is not correct for some reason. To make things easier, I've connected the objects using control-drag, which adds the required code automatically. So first I've connected the Text View. To do this click on the text view object until Text View is selected. When I do this in my version of Xcode, the first time I click on the object, Bordered Scroll View is selected. Clicking on it again then selects Clip View. Finally, clicking on it again selects Text View. Now I control-drag from the object to the AppDelegate.swift code (It helps to display the Assistant Editor so that you have your form UI and code side-by-side).
By doing this I get this little window:
Notice that the type is NSTextView and the storage is Weak. I've only had to add the name and click Connect. This adds the following code in AppDelegate.swift:
#IBOutlet var textField: NSTextView!
The code is almost exactly like the one you have, except for the ! at the end of the line, which forces to unwrap the value of textField.
Just with that, the code as you have it in your question should work.
The other thing I would suggest is not to use insertText. According to Apple's documentation for NSTextView.insertText:
This method is the entry point for inserting text typed by the user
and is generally not suitable for other purposes. Programmatic
modification of the text is best done by operating on the text storage
directly.
As far as I understand this, programmatic modification of the text by operating on the text storage directly means dealing with NSText, which NSTextView inherits from. So instead, use NSText.string. This is how the click button action looks in my code:
#IBAction func displaySomeText(sender: NSButton) {
// If you want to add a new 'A string... ' every time you click the button
textField.string! += "A string... "
// otherwise just use
//textField.string = "A string..."
}
I have added the Button Action in the same way as I've added the Text View Outlet, by control-dragging, and, in this case, selecting NSButton as the sender, instead of leaving the default AnyObject.
#IBOutlet automatically makes a property weak IIRC, but weak doesn't automatically make a property optional. But it is required that a weak property be made optional, as the property could at any time be deallocated and made nil. So you have to declare your #IBOutlets as optional.
import Cocoa
class AppDelegate: NSObject, NSApplicationDelegate {
#IBOutlet var window: NSWindow? // Optional
#IBOutlet var textField: NSTextView?
#IBAction func displaySomeText(AnyObject) {
textField?.insertText("A string...") // Only call the method if the object exists
}
func applicationDidFinishLaunching(aNotification: NSNotification?) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification?) {
// Insert code here to tear down your application
}
}
Does the "dong" error suggest a responder chain problem? What if you call becomeFirstResponder on the text field before inserting the text?
To create a weak reference user the weak keyword:
example:
#IBOutlet weak var myView: UIView
In your case
#IBOutlet weak var textField: NSTextView