Compare commits
No commits in common. "7f0fd119c5df56ce6e678a12c54a45cc0321af63" and "f86d3a0ed15ac23a77c47d9f56deb91e2eba661c" have entirely different histories.
7f0fd119c5
...
f86d3a0ed1
|
@ -16,12 +16,6 @@ extension UIBezierPath {
|
||||||
/// and draws a line around the outer borders of the combined shape.
|
/// and draws a line around the outer borders of the combined shape.
|
||||||
convenience init(wrappingAround rects: [CGRect]) {
|
convenience init(wrappingAround rects: [CGRect]) {
|
||||||
precondition(rects.count > 0)
|
precondition(rects.count > 0)
|
||||||
|
|
||||||
if rects.count == 1 {
|
|
||||||
self.init(rect: rects.first!)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let rects = rects.sorted { $0.minY < $1.minY }
|
let rects = rects.sorted { $0.minY < $1.minY }
|
||||||
|
|
||||||
self.init()
|
self.init()
|
||||||
|
|
|
@ -30,20 +30,7 @@ class LocalData: ObservableObject {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
defaults = UserDefaults(suiteName: "group.space.vaccor.Tusker")!
|
defaults = UserDefaults()
|
||||||
tryMigrateOldDefaults()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: remove me before public beta
|
|
||||||
private func tryMigrateOldDefaults() {
|
|
||||||
let old = UserDefaults()
|
|
||||||
if let accounts = old.array(forKey: accountsKey) as? [[String: String]],
|
|
||||||
let mostRecentAccount = old.string(forKey: mostRecentAccountKey) {
|
|
||||||
defaults.setValue(accounts, forKey: accountsKey)
|
|
||||||
defaults.setValue(mostRecentAccount, forKey: mostRecentAccountKey)
|
|
||||||
old.removeObject(forKey: accountsKey)
|
|
||||||
old.removeObject(forKey: mostRecentAccountKey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
class MyProfileTableViewController: ProfileTableViewController {
|
class MyProfileTableViewController: ProfileTableViewController {
|
||||||
|
|
||||||
|
@ -31,18 +32,14 @@ class MyProfileTableViewController: ProfileTableViewController {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preferences", style: .plain, target: self, action: #selector(preferencesPressed))
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder aDecoder: NSCoder) {
|
required init?(coder aDecoder: NSCoder) {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func viewDidLoad() {
|
|
||||||
super.viewDidLoad()
|
|
||||||
|
|
||||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Preferences", style: .plain, target: self, action: #selector(preferencesPressed))
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc func preferencesPressed() {
|
@objc func preferencesPressed() {
|
||||||
present(PreferencesNavigationController(mastodonController: mastodonController), animated: true)
|
present(PreferencesNavigationController(mastodonController: mastodonController), animated: true)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,6 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>group.space.vaccor.Tusker</string>
|
|
||||||
</array>
|
|
||||||
<key>com.apple.security.device.audio-input</key>
|
<key>com.apple.security.device.audio-input</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.device.camera</key>
|
<key>com.apple.security.device.camera</key>
|
||||||
|
|
|
@ -22,11 +22,6 @@ class ContentTextView: LinkTextView {
|
||||||
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
var defaultFont: UIFont = .systemFont(ofSize: 17)
|
||||||
var defaultColor: UIColor = .label
|
var defaultColor: UIColor = .label
|
||||||
|
|
||||||
// The link range currently being previewed
|
|
||||||
private var currentPreviewedLinkRange: NSRange?
|
|
||||||
// The preview created in the previewForHighlighting method, so that we can use the same one in previewForDismissing.
|
|
||||||
private weak var currentTargetedPreview: UITargetedPreview?
|
|
||||||
|
|
||||||
override func awakeFromNib() {
|
override func awakeFromNib() {
|
||||||
super.awakeFromNib()
|
super.awakeFromNib()
|
||||||
|
|
||||||
|
@ -261,8 +256,11 @@ extension ContentTextView: MenuPreviewProvider {
|
||||||
extension ContentTextView: UIContextMenuInteractionDelegate {
|
extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, configurationForMenuAtLocation location: CGPoint) -> UIContextMenuConfiguration? {
|
||||||
if let (link, range) = getLinkAtPoint(location) {
|
if let (link, range) = getLinkAtPoint(location) {
|
||||||
// Store the previewed link range for use in the previewForHighlighting method
|
// Determine the line rects that the link takes up in the coordinate space of this view
|
||||||
currentPreviewedLinkRange = range
|
var rects = [CGRect]()
|
||||||
|
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
|
||||||
|
rects.append(rect)
|
||||||
|
}
|
||||||
|
|
||||||
let preview: UIContextMenuContentPreviewProvider = {
|
let preview: UIContextMenuContentPreviewProvider = {
|
||||||
self.getViewController(forLink: link, inRange: range)
|
self.getViewController(forLink: link, inRange: range)
|
||||||
|
@ -280,53 +278,66 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||||
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
return UIMenu(title: "", image: nil, identifier: nil, options: [], children: actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UIContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
|
// Use a custom UIContentMenuConfiguration subclass to pass the text line rect information
|
||||||
|
// to the `contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)` method.
|
||||||
|
let configuration = ContextMenuConfiguration(identifier: nil, previewProvider: preview, actionProvider: actions)
|
||||||
|
configuration.textLineRects = rects
|
||||||
|
return configuration
|
||||||
} else {
|
} else {
|
||||||
currentPreviewedLinkRange = nil
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForHighlightingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
||||||
// If there isn't a link range, use the default system-generated preview.
|
// If there isn't custom text line rect data, use the default system-generated preview.
|
||||||
guard let range = currentPreviewedLinkRange else {
|
guard let config = configuration as? ContextMenuConfiguration,
|
||||||
|
let rects = config.textLineRects,
|
||||||
|
rects.count > 0 else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
currentPreviewedLinkRange = nil
|
|
||||||
|
|
||||||
// Determine the line rects that the link takes up in the coordinate space of this view.
|
|
||||||
var rects = [CGRect]()
|
|
||||||
layoutManager.enumerateEnclosingRects(forGlyphRange: range, withinSelectedGlyphRange: NSRange(location: NSNotFound, length: 0), in: textContainer) { (rect, stop) in
|
|
||||||
rects.append(rect)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to create a snapshot view of this view to disply as the preview.
|
|
||||||
// If a snapshot view cannot be created, we bail and use the system-provided preview.
|
|
||||||
guard let snapshot = self.snapshotView(afterScreenUpdates: false) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mask the snapshot layer to only show the text of the link, and nothing else.
|
|
||||||
// By default, the system-applied mask is too wide and other content may seep in.
|
|
||||||
let path = UIBezierPath(wrappingAround: rects)
|
|
||||||
let maskLayer = CAShapeLayer()
|
|
||||||
maskLayer.path = path.cgPath
|
|
||||||
snapshot.layer.mask = maskLayer
|
|
||||||
|
|
||||||
// The preview parameters describe how the preview view is shown inside the preview.
|
|
||||||
let parameters = UIPreviewParameters(textLineRects: rects as [NSValue])
|
|
||||||
|
|
||||||
// Calculate the smallest rect enclosing all of the text line rects, in the coordinate space of this view.
|
// Calculate the smallest rect enclosing all of the text line rects, in the coordinate space of this view.
|
||||||
var minX: CGFloat = .greatestFiniteMagnitude, maxX: CGFloat = -.greatestFiniteMagnitude, minY: CGFloat = .greatestFiniteMagnitude, maxY: CGFloat = -.greatestFiniteMagnitude
|
var minX: CGFloat = .greatestFiniteMagnitude, maxX: CGFloat = .leastNonzeroMagnitude, minY: CGFloat = .greatestFiniteMagnitude, maxY: CGFloat = .leastNonzeroMagnitude
|
||||||
for rect in rects {
|
for rect in rects {
|
||||||
minX = min(rect.minX, minX)
|
minX = min(rect.minX, minX)
|
||||||
maxX = max(rect.maxX, maxX)
|
maxX = max(rect.maxX, maxX)
|
||||||
minY = min(rect.minY, minY)
|
minY = min(rect.minY, minY)
|
||||||
maxY = max(rect.maxY, maxY)
|
maxY = max(rect.maxY, maxY)
|
||||||
}
|
}
|
||||||
|
let rectEnclosingTextLineRects = CGRect(x: minX, y: minY, width: maxX - minX, height: maxY - minY)
|
||||||
|
|
||||||
|
// Try to create a snapshot view of this view that only shows the minimum
|
||||||
|
// rectangle necessary to fully display the link text (reduces the likelihood that
|
||||||
|
// other text will be displayed alongside it).
|
||||||
|
// If a snapshot view cannot be created, we bail and use the system-provided preview.
|
||||||
|
guard let snapshot = self.resizableSnapshotView(from: rectEnclosingTextLineRects, afterScreenUpdates: false, withCapInsets: .zero) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the textLineRects from the context menu configuration to be in the
|
||||||
|
// coordinate space of the snapshot view. The snapshot view is created from
|
||||||
|
// rectEnclosingTextLineRects, which means that, while its size is the same as the
|
||||||
|
// enclosing rect, its coordinate space is relative to this text views by rectEnclosingTextLineRects.origin.
|
||||||
|
// Since the text line rects passed to UIPreviewParameters need to be in the coordinate space of
|
||||||
|
// the preview view, we subtract the origin position from each rect to convert to the snapshot view's
|
||||||
|
// coordinate space.
|
||||||
|
let rectsInCoordinateSpaceOfEnclosingRect = rects.map {
|
||||||
|
$0.offsetBy(dx: -rectEnclosingTextLineRects.minX, dy: -rectEnclosingTextLineRects.minY)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The preview parameters describe how the preview view is shown inside the prev.
|
||||||
|
let parameters = UIPreviewParameters(textLineRects: rectsInCoordinateSpaceOfEnclosingRect as [NSValue])
|
||||||
|
|
||||||
|
// Mask the snapshot layer to only show the text of the link, and nothing else.
|
||||||
|
// By default, the system-applied mask is too wide and other content may seep in.
|
||||||
|
let path = UIBezierPath(wrappingAround: rectsInCoordinateSpaceOfEnclosingRect)
|
||||||
|
let maskLayer = CAShapeLayer()
|
||||||
|
maskLayer.path = path.cgPath
|
||||||
|
snapshot.layer.mask = maskLayer
|
||||||
|
|
||||||
// The center point of the the minimum enclosing rect in our coordinate space is the point where the
|
// The center point of the the minimum enclosing rect in our coordinate space is the point where the
|
||||||
// center of the preview should be, since that's also in this view's coordinate space.
|
// center of the preview should be, since that's also in this view's coordinate space.
|
||||||
let rectsCenter = CGPoint(x: (minX + maxX) / 2, y: (minY + maxY) / 2)
|
let rectsCenter = CGPoint(x: rectEnclosingTextLineRects.midX, y: rectEnclosingTextLineRects.midY)
|
||||||
|
|
||||||
// The preview target describes how the preview is positioned.
|
// The preview target describes how the preview is positioned.
|
||||||
let target = UIPreviewTarget(container: self, center: rectsCenter)
|
let target = UIPreviewTarget(container: self, center: rectsCenter)
|
||||||
|
@ -336,14 +347,7 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||||
let snapshotContainer = UIView(frame: snapshot.bounds)
|
let snapshotContainer = UIView(frame: snapshot.bounds)
|
||||||
snapshotContainer.addSubview(snapshot)
|
snapshotContainer.addSubview(snapshot)
|
||||||
|
|
||||||
let preview = UITargetedPreview(view: snapshotContainer, parameters: parameters, target: target)
|
return UITargetedPreview(view: snapshotContainer, parameters: parameters, target: target)
|
||||||
currentTargetedPreview = preview
|
|
||||||
return preview
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, previewForDismissingMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? {
|
|
||||||
// Use the same preview for dismissing as was used for highlighting, so that the link animates back to the original position.
|
|
||||||
return currentTargetedPreview
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
func contextMenuInteraction(_ interaction: UIContextMenuInteraction, willPerformPreviewActionForMenuWith configuration: UIContextMenuConfiguration, animator: UIContextMenuInteractionCommitAnimating) {
|
||||||
|
@ -354,4 +358,10 @@ extension ContentTextView: UIContextMenuInteractionDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Used to pass text line rect data between `contextMenuInteraction(_:configurationForMenuAtLocation:)` and `contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)`
|
||||||
|
fileprivate class ContextMenuConfiguration: UIContextMenuConfiguration {
|
||||||
|
/// The line rects of the source of this context menu configuration in the coordinate space of the preview target view.
|
||||||
|
var textLineRects: [CGRect]?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue