Open16
Swift: Extension
最強の標準出力
func logput(_ items: Any...,
file: String = #file,
line: Int = #line,
function: String = #function) {
#if DEBUG
let fileName = URL(fileURLWithPath: file).lastPathComponent
var array: [Any] = ["💫Log: \(fileName)", "Line:\(line)", function]
array.append(contentsOf: items)
Swift.print(array)
#endif
}
他言語対応
extension String {
var localized: String {
return NSLocalizedString(self, comment: self)
}
}
URLからファイル名を取得
extension URL {
var fileName: String {
return self.deletingPathExtension().lastPathComponent
}
}
画像の正しいピクセルサイズでNSImageを初期化
extension NSImage {
convenience init(name: String) {
self.init(imageLiteralResourceName: name)
let rep = representations[0]
size = CGSize(width: rep.pixelsWide, height: rep.pixelsHigh)
}
}
NSControl.StateValue と Bool の相互変換
extension Bool {
var state: NSControl.StateValue {
return self ? .on : .off
}
}
extension NSControl.StateValue {
var isOn: Bool {
return self == .on
}
}
ダークモードかどうかの取得(macOS)
extension NSAppearance {
var isDark: Bool {
if self.name == .vibrantDark { return true }
guard #available(macOS 10.14, *) else { return false }
return self.bestMatch(from: [.aqua, .darkAqua]) == .darkAqua
}
}
Int の桁数を取得する
extension Int {
var digit: Int {
assert(0 < self, "The number must be a natural number.")
return Int(floor(log10(Double(self)))) + 1
}
}
NSMenuItem に target と selector を渡す
extension NSMenuItem {
func setAction(target: AnyObject, selector: Selector) {
self.target = target
self.action = selector
}
}
NSScreen から DisplayID を取得する
extension NSScreen {
var displayID: CGDirectDisplayID {
let key = NSDeviceDescriptionKey(rawValue: "NSScreenNumber")
return deviceDescription[key] as! CGDirectDisplayID
}
}
DisplayID と WindowID から任意のウィンドウの背景画像を取得する
extension CGImage {
static func background(displayID: CGDirectDisplayID, windowID: CGWindowID) -> CGImage? {
let bounds = CGDisplayBounds(displayID)
let windowOptions: CGWindowListOption = [
.optionOnScreenOnly,
.optionOnScreenBelowWindow
]
let imageOptions: CGWindowImageOption = [
.bestResolution,
.boundsIgnoreFraming
]
return CGWindowListCreateImage(bounds, windowOptions, windowID, imageOptions)
}
}
NSView のサムネイルを作成
extension NSView {
var thumbnail: NSImage? {
guard let rep = bitmapImageRepForCachingDisplay(in: bounds) else { return nil }
rep.size = bounds.size
cacheDisplay(in: bounds, to: rep)
guard let data = rep.representation(using: .png, properties: [:]) else { return nil }
return NSImage(data: data)
}
}
NSColor の色見本を取得する・NSColorの成分を出力する
extension NSColor {
var swatch: NSImage {
let size = NSSize(width: 18.0, height: 18.0)
let rect = NSRect(origin: .zero, size: size)
let image = NSImage(size: size)
image.lockFocus()
self.drawSwatch(in: rect)
image.unlockFocus()
let swatch = NSImage(size: size)
swatch.lockFocus()
let path = NSBezierPath(roundedRect: rect, xRadius: 3, yRadius: 3)
path.windingRule = .evenOdd
path.addClip()
image.draw(at: .zero, from: rect, operation: .sourceOver, fraction: 1)
swatch.unlockFocus()
return swatch
}
func printComponents() {
print("🌈", self.colorSpace.localizedName ?? "unknown")
print("Number of Components:", self.colorSpace.numberOfColorComponents)
switch self.colorSpace {
case .genericGray, .deviceGray, .genericGamma22Gray, .extendedGenericGamma22Gray:
print("- white", self.whiteComponent)
case .genericRGB, .deviceRGB, .sRGB, .extendedSRGB, .displayP3, .adobeRGB1998:
print("- red", self.redComponent)
print("- green", self.greenComponent)
print("- blue", self.blueComponent)
print("- hue", self.hueComponent)
print("- saturation:", self.saturationComponent)
print("- brightness:", self.brightnessComponent)
case .genericCMYK, .deviceCMYK:
print("- cyan", self.cyanComponent)
print("- magenta", self.magentaComponent)
print("- yellow", self.yellowComponent)
print("- black", self.blackComponent)
default:
break
}
print("- alpha:", self.alphaComponent)
print()
}
}
正規表現にマッチした箇所を指定した文字列で置換
extension String {
func replace(pattern: String, expect: String) -> String {
return self.replacingOccurrences(of: pattern,
with: expect,
options: .regularExpression,
range: self.range(of: self))
}
}
print("Hello :NAME:".replace(pattern: #":NAME:"#, expect: "Mike")) // Hello Mike
print("http://hoge.com/".replace(pattern: #"^https?://"#, expect: "")) // hoge.com/
print("AAA--BbB--CCC".replace(pattern: #"[ABC]{3}"#, expect: "🐷")) // 🐷--BbB--🐷
正規表現のパターンを記入するときは#""#
で囲むとエスケープしなくていいので楽。
引数の正規表現にマッチしている文字列かどうか確認
完全一致
extension String {
func match(pattern: String) -> Bool {
let selfRange = self.startIndex ..< self.endIndex
let matchRange = self.range(of: pattern, options: .regularExpression)
return selfRange == matchRange
}
}
let str = "abcdefg"
print(str.match(pattern: #"[a-z]+"#)) // true
print(str.match(pattern: #"[A-Z]+"#)) // false
部分一致
extension String {
func match(pattern: String) -> Bool {
let matchRange = self.range(of: pattern, options: .regularExpression)
return matchRange != nil
}
}
Retina 環境でも正確に NSImage をリサイズする
extension CGFloat {
var roundedInt: Int {
return Int(self.rounded())
}
}
extension NSImage {
var ppi: CGFloat {
let rep = self.representations[0]
return (72.0 * CGFloat(rep.pixelsWide) / self.size.width)
}
var pixelSize: CGSize {
let rep = self.representations[0]
return CGSize(width: rep.pixelsWide, height: rep.pixelsHigh)
}
func resized(with ratio: CGFloat) -> NSImage {
let pixelSize = self.pixelSize
let newPixelsWide: Int = (ratio * pixelSize.width).roundedInt
let newPixelsHigh: Int = (ratio * pixelSize.height).roundedInt
let sourceRep = NSBitmapImageRep(data: self.tiffRepresentation!)!
let resizedRep = NSBitmapImageRep(bitmapDataPlanes: nil,
pixelsWide: newPixelsWide,
pixelsHigh: newPixelsHigh,
bitsPerSample: sourceRep.bitsPerSample,
samplesPerPixel: sourceRep.samplesPerPixel,
hasAlpha: sourceRep.hasAlpha,
isPlanar: sourceRep.isPlanar,
colorSpaceName: sourceRep.colorSpaceName,
bytesPerRow: sourceRep.bytesPerRow,
bitsPerPixel: sourceRep.bitsPerPixel)!
let ppi = self.ppi
let newSize = NSSize(width: CGFloat(newPixelsWide) * 72.0 / ppi,
height: CGFloat(newPixelsHigh) * 72.0 / ppi)
resizedRep.size = newSize
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.current = NSGraphicsContext(bitmapImageRep: resizedRep)
self.draw(in: NSRect(origin: .zero, size: newSize))
NSGraphicsContext.restoreGraphicsState()
let image = NSImage(size: newSize)
image.addRepresentation(resizedRep)
return image
}
}
画像を保存する
extension NSImage {
func saveFile(at url: URL, fileName: String, fileType: NSBitmapImageRep.FileType) {
guard let data = self.tiffRepresentation,
let bitmapRep = NSBitmapImageRep(data: data),
let imageData = bitmapRep.representation(using: fileType, properties: [:])
else { return }
let fileExtension: String
switch fileType {
case .tiff: fileExtension = "tiff"
case .bmp: fileExtension = "bmp"
case .gif: fileExtension = "gif"
case .jpeg: fileExtension = "jpg"
case .png: fileExtension = "png"
case .jpeg2000: fileExtension = "jp2"
@unknown default: fileExtension = ""
}
var saveURL = url.appendingPathComponent(fileName).appendingPathExtension(fileExtension)
var cnt = 2
while FileManager.default.fileExists(atPath: saveURL.path) {
saveURL = url.appendingPathComponent("\(fileName) \(cnt)").appendingPathExtension(fileExtension)
cnt += 1
}
try? imageData.write(to: saveURL, options: .atomic)
}
}
Debug用のLog
func DebugLog(_ instance: AnyClass, _ message: String) {
#if DEBUG
let type = String(describing: type(of: instance))
NSLog("🛠 \(type): \(message)")
#endif
}