スクリプトでOBSにカウンタを導入する

昨日の夜〜早朝に配信していたVTuber(御伽原江良さん)の耐久ゲーム配信を6時間ほどみてたんですが、
リトライ毎に挑戦回数のテキストを編集するのが大変そうだったのでカウントを補助するOBSのスクリプトを作りました。

スクリプトをダウンロードしてOBSに登録すれば、
ワンクリック(ホットキー登録すればキー1回押し)でカウントできるようになります。

リトライ回数やキル数など、配信で表示する汎用の手動カウンタとして使えます。

設定方法

  1. スクリプトここからダウンロードして解凍。
    保存場所はどこでも。
  2. OBSのメニュー ツール > スクリプト f:id:tid-a24:20190623203936p:plain

  3. スクリプトウィンドウの+ボタンを押してDLしたスクリプトを選択して登録 f:id:tid-a24:20190623204010p:plain

f:id:tid-a24:20190623204149p:plain

使い方

  1. シーンにテキストソースを登録 f:id:tid-a24:20190623204600p:plain

  2. OBSのメニュー ツール > スクリプト f:id:tid-a24:20190623204641p:plain

  3. 登録したスクリプトを選択、スクリプトをリロード(リロードしないとテキストソースが反映されません) f:id:tid-a24:20190624164225p:plain

  4. Text Sourceに作ったテキストソースを設定 f:id:tid-a24:20190623211943p:plain

  5. Formatを好きな文字列に変更。(例: 今%d回目) ※%dが現在のカウントに置き換えられます。 f:id:tid-a24:20190623205325p:plain

  6. UP / DOWNボタンをクリックするとカウントアップ(ダウン) f:id:tid-a24:20190623205432p:plain

  7. Resetボタンをクリックするとカウントリセット f:id:tid-a24:20190623212127p:plain

テキストのスタイルはテキストソースの設定が使われます。

ホットキー登録

ホットキー登録するとスクリプトウィンドウを開かずにキーボードのキー1回押しでカウントアップ(ダウン)とリセットができます。
個人で使いやすいキーを登録してください。

f:id:tid-a24:20190623213334p:plain f:id:tid-a24:20190623213511p:plain

その他の設定

Start Number:開始数字。
Step Number:カウントの刻み。この数値分増減します。

コード

[OBS Script]Sets a text source to act as a tally counter when the source is active. · GitHub

ライセンス

商用・非商用問わず自己責任でご自由にお使いください。
このスクリプトを使用したことで損害が発生しても責任は一切負いません。

おわり

御伽原さんの耐久ゲーム配信のアーカイブはこちら www.youtube.com

WWDC19 とりあえず見たいセッションリスト

概要

Keynote
Platforms State of the Union

Xcode

What’s New in Xcode 11
Getting Started with Xcode
Adopting Swift Packages in Xcode
Debugging in Xcode 11
Testing in Xcode
Creating Great Localized Experiences with Xcode 11
Great Developer Habits

Swift

What’s New in Swift
Modern Swift API Design
Creating Swift Packages
Swift Playgrounds 3

Combine

Combine in Practice
Introducing Combine
Introducing Combine and Advances in Foundation

SwiftUI

Building Custom Views with SwiftUI
Data Flow Through SwiftUI
Accessibility in SwiftUI
Integrating SwiftUI
Introducing SwiftUI: Building Your First App
SwiftUI Essentials
SwiftUI On All Devices
SwiftUI on watchOS
Mastering Xcode Previews

Design

What’s New in iOS and macOS Design
What’s New in iOS Design
Modernizing Your UI for iOS 13

AppKit

What’s New in AppKit for macOS

その他

What’s New in Clang and LLVM
What’s New in App Store Connect

RxSwiftを利用している方がCombineに注目しているようでしたが、現時点ではまだRxのサブセットのように見えました。 iOS 13だけサポートするとしても今すぐRxSwiftを置き換えることはできないと思います。 ただ、iOS 12のサポートを切れる頃にはもっと成長してそうなので今後に期待です。(・・・2年後かな。)

CombineはSwiftUIでも利用するので、Combine → SwiftUIの順に見ていくのが良さそうです。

追記: Shai Mishali さんがRxSwiftと比較したCombineのチートシートを作られています。 medium.com

これを拝見する限り、思ったより網羅されているようです。
一部、?と思ってしまう名前のものがあるので治してくれるといいですね。

アラートを表示する(NSAlert)

f:id:tid-a24:20190315214917p:plain

アラートの表示

アラートを作る

let alert = NSAlert()

ダイアログとして表示

f:id:tid-a24:20190315220244p:plain

alert.runModal()
押されたボタンを確認

runModal()の戻り値で確認できます。

let response = alert.runModal()
switch response {
case .alertFirstButtonReturn:
    break
case .alertSecondButtonReturn:
    break
case .alertThirdButtonReturn:
    break
default:
    break
}

シートとして表示する

f:id:tid-a24:20190315220318p:plain

alert.beginSheetModal(for: window, completionHandler: nil)
押されたボタンを確認する

シート表示の第2引数のクロージャで確認できます。

alert.beginSheetModal(for: window) { response in
    switch response {
    case .alertFirstButtonReturn:
        break
    case .alertSecondButtonReturn:
        break
    case .alertThirdButtonReturn:
        break
    default:
        break
    }
}

アラートのカスタマイズ

アイコン

デフォルトではアプリのアイコンが使用されますが変更可能です。

f:id:tid-a24:20190315220120p:plain

alert.icon = NSImage(named: “Awesome icon”)

スタイル

スタイルは下記の3種類が用意されています。 今のところinformationalとwarningは見た目上の違いはないそうです。 criticalについてはmacOS Human Interface Guidelinesの”Alerts”の章に従って使用するようにします。

informational

f:id:tid-a24:20190315220120p:plain

alert.alertStyle = .informational
warning

f:id:tid-a24:20190315220120p:plain

alert.alertStyle = .warning

critical

f:id:tid-a24:20190315215243p:plain

alert.alertStyle = .critical

メッセージ

f:id:tid-a24:20190315215535p:plain

  • タイトル変更
alert.messageText = “Main Message”
  • 追加テキスト変更
alert.informativeText = “Additional information text”

ボタン

addButton(withTitle:)でボタンを追加できます。 これを使うとデフォルトのボタンは使われなくなります。 1回addButton(withTitle:)を呼ぶと1個、2回呼ぶと2個…という風にボタンが増えていきます。 任意の数のボタンを追加できますが、3個までが想定されている個数のようです。

ここでは2個のボタンを追加する例をあげます。

f:id:tid-a24:20190315215525p:plain

alert.addButton(withTitle: “OK”)
alert.addButton(withTitle: “Cancel”)

抑制ボタン

“次からこのメッセージを表示しない。”チェックボックスを表示できます。

f:id:tid-a24:20190315215902p:plain

alert.showsSuppressionButton = true
抑制ボタンのメッセージを変更

抑制ボタンはNSButtonなのでメッセージを変更できます。

f:id:tid-a24:20190315215332p:plain

alert.suppressionButton?.title = “No thank you”

アクセサリービュー

独自のビューを表示したい時はアクセサリービューとして設定します。 アクセサリービューはNSViewならなんでも表示できます。 抑制ボタンとボタンの間に表示されます。

f:id:tid-a24:20190315220014p:plain

let textView = NSTextView(frame: NSRect(x: 0, y: 0, width: 200, height: 150))
textView.string = “TEXT IN ACCESSORYVIEW”
alert.accessoryView = textView

参考

developer.apple.com

developer.apple.com

NSTextViewで折りたたみ

NSTextViewで折りたたみ(フォールディング)は、折りたたみたい文字のグリフをNullGlyphに置き換えることで実現できます。

下記に手順を紹介します。

折りたたむ文字列に目印をつける

折りたたみたい文字に目印となるカスタム属性を設定します。 ここではカスタム属性のキーとして下記の.foldingを定義しているものとします。

// カスタムAttribute
extension NSAttributedString.Key {
    static let folding = NSAttributedString.Key(rawValue: “NSAttributedString.Key.folding”)
}

例えば”ABCD”という文字列があったとして、”BC”を折りたたみたい場合は”BC”の範囲にカスタム属性.foldingを設定します。

textView.string = “ABCD”
textView.storage?.addAttributes([.folding: true], range: NSRange(location: 1, length: 2))

カスタム属性を設定しただけでは何も起こりません。 次にカスタム属性を目印にしてグリフをNullGlyphに置き換えるようにします。

グリフをNullGlyphに置き換える

カスタム属性を設定した箇所のグリフを変更します。 グリフはNSLayoutManagerで設定するのでサブクラスを作ってグリフを設定するメソッドをオーバーライドします。

class LayoutManager: NSLayoutManager {
    override func setGlyphs(_ glyphs: UnsafePointer<CGGlyph>,
                            properties props: UnsafePointer<NSLayoutManager.GlyphProperty>,
                            characterIndexes charIndexes: UnsafePointer<Int>,
                            font aFont: NSFont,
                            forGlyphRange glyphRange: NSRange) {
        // Glyphプロパティで.nullを指定すると該当箇所のGlyphがNULLGlyphとして扱われる
        var properties = [NSLayoutManager.GlyphProperty](repeating: .null, count: glyphRange.length)

        for i in 0..<glyphRange.length {
            let index = glyphRange.location + i
            if let attrs = textStorage?.attributes(at: index, effectiveRange: nil),
                attrs[.folding] == nil {
                properties[i] = props.advanced(by: i).pointee
            }
        }

        super.setGlyphs(glyphs, properties: &properties, characterIndexes: charIndexes, font: aFont, forGlyphRange: glyphRange)
    }
}

次にここで作ったカスタムLayoutManagerをNSTextViewが利用するように設定します。

カスタムLayoutManagerを使用する

NSTextViewのインスタンスを作る時に設定します。

let textStorage   = NSTextStorage()
let layoutManager = LayoutManager()

let textContainer = NSTextContainer()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
NSTextView(frame: frame, textContainer: textContainer)

折りたたみを解除するには?

カスタム属性を消去すれば折りたたみは解除されます。

textView.textStorage?.removeAttribute(.folding, range: NSRange(location: 1, length: 2))

まとめ

基本的にiOS(UITextView)でも同じ考え方で折りたたみを実装できます。(メソッドや引数が異なる程度の差です。) また、この方法の応用でIDEによくあるラインフォールディングも実装できます。 折りたたみの範囲を行単位にするだけです。

NSTextViewで文字列にリンクを設定する

任意の文字列にリンクを設定

NSTextStorageにキーがNSAttributedString.Key.linkで値がURLの属性を設定すれば任意の文字列にリンクを設定できます。

f:id:tid-a24:20190313225624p:plain

textView.string = "Link to Blog"
let url = URL(string: "https://tid-a24.hatenablog.com/")!
textView.textStorage?.addAttributes([.link: url], range: NSRange(textView.string))

URL文字列に自動でリンクを設定

NSTextViewにURL文字列が含まれて、そのURL文字列をリンクとして扱いたいなら
isAutomaticLinkDetectionEnabledプロパティをtrueにすれば自動でリンクとして設定されます。

f:id:tid-a24:20190313224546p:plain

textView.isAutomaticLinkDetectionEnabled = true

リンク属性の設定

リンクテキストの文字色やマウスオーバー時のカーソル形状などを変更するにはlinkTextAttributesプロパティに値を設定します。 デフォルトでは文字色はNSColor.linkColorでアンダーラインが付いたものになり、マウスオーバー時のカーソル形状は指差しハンドです。

  • マウスオーバー時のカーソル形状指定
textView.linkTextAttributes?[.cursor] = NSCursor.pointingHand
  • アンダーラインを消す
linkTextAttributes?[NSAttributedString.Key.underlineStyle] = nil

NSPopoverを閉じる方法

f:id:tid-a24:20190312201458p:plain

BehaviorでNSPopoverの閉じる方法(挙動)を指定します。

let popover = NSPopover()
popover.behavior = .transient

Behaviorは3種類定義されています。

  1. applicationDefined
    開発者が動作を定義する。(閉じるボタンをつけるなど。)
    自動で閉じることはない。

  2. transient
    NSPopoverからフォーカスが外れると自動で閉じる。

  3. semitransient
    transientと同じだが、他Window(他のアプリケーション含む)へのフォーカス移動では閉じない。
    また、他Windowへフォーカスを移動した後にNSPopoverを表示しているWindowへフォーカスを移動しても閉じない。

基本的にtransientを使用して、NSPopoverを表示したまま他Windowを操作する必要があればsemitransientを使えばいいように思います。