exclusiveTouch
ですclipsToBounds
が好きです。isUserInteractionEnabled
と友達で、よく喧嘩します!/// viewControllerから再帰的に画面遷移でtoViewControllerまで戻る fileprivate func recursivelyDismissViewController( _ viewController: UIViewController, toViewController: UIViewController, completion: @escaping (_ dismissedViewController: UIViewController) -> Void) { guard viewController != toViewController else { completion(viewController) return } guard toViewController != self.rootViewController else { completion(viewController) return } if let presentingViewController = viewController.presentingViewController { viewController.dismiss(animated: false, completion: { [weak self] in self?.recursivelyDismissViewController( presentingViewController, toViewController: toViewController, completion: completion) }) } else if viewController.navigationController?.viewControllers.contains(toViewController) ?? false { viewController.navigationController? ._transition_router_popToViewController( toViewController, animated: false) { completion(toViewController) } } else if let nvc = viewController as? UINavigationController, nvc.viewControllers.contains(toViewController) { nvc._transition_router_popToViewController(toViewController, animated: false) { completion(toViewController) } } else { completion(viewController) } }
(edited)enum ViewControllerInteractiveEvent { case popGestureCompleted case pushedOrPoped case cancelled } extension UIViewController { func hoge_receiveInteractiveAction(action: ((ViewControllerInteractiveEvent) -> Void)?) { if let coordinator = self.transitionCoordinator, coordinator.initiallyInteractive { coordinator.notifyWhenInteractionEnds({ context in if context.isCancelled { action?(.cancelled) } else { action?(.popGestureCompleted) } }) } else { action?(.pushedOrPoped) } } }
(edited) @IBAction func onButton() { var message = "" for _ in 0..<1000 { message += "長い文言" } let alertController = UIAlertController(title: "aaa", message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "button1", style: .default) { print($0) }) alertController.addAction(UIAlertAction(title: "button2", style: .default) { print($0) }) alertController.addAction(UIAlertAction(title: "cancel", style: .cancel) { print($0) }) present(alertController, animated: true, completion: nil) }
iOS 10.3 to iOS 11.0 API Differences
で検索してみたらそれっぽいのが。↑tabBarController.tabBar.isHidden = true tabBarController.additionalSafeAreaInsets.bottom += 1.0 tabBarController.additionalSafeAreaInsets.bottom -= 1.0 tabBarController.view.layoutIfNeeded()
これやると、SafeAreaが再計算されます (edited)self.additionalSafeAreaInsets.bottom += 1.0 self.additionalSafeAreaInsets.bottom -= 1.0
wwautoresizingMask = [.flexibleHeight, .flexibleWidth]
にして自己解決しました。やったぜUINavigationController
のviewControllers
(もしくはtopViewController
or visibleViewController
)の変化をKVO(UINavigationControllerDelegate
以外)で検出したいと思っているのですが、可能かどうかご存じの方いらっしゃいますでしょうか? 以下のコードで試したのですが、push / popしても何も反応ありませんでした。 class CustomNavigationController: UINavigationController { var token: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() token = self.observe(\.viewControllers) { observed, change in print(observed) print(change) } } }
根源的にやりたいことは次のようなものです: status barのappearanceの変化のために、childViewControllerForStatusBarHidden
をoverrideし、topViewControllerを返すようにしています。 class CustomNavigationController: UINavigationController { override var childViewControllerForStatusBarHidden: UIViewController? { return self.topViewController } }
このメソッドのドキュメントを見ると、 If you change the return value from this method, call the setNeedsStatusBarAppearanceUpdate() method.
とあり、topViewControllerが変わった段階で setNeedsStatusBarAppearanceUpdate
をcallする必要があるので、変化を検出したいです。 (edited)"UINavigationControllerDidShowViewControllerNotification"
という通知を監視すると要求は満たせると思います。問題はUndocumentedなことですけど。Plain Style unsupported in a Navigation Item
warningについて質問です IB上でUIBarButtonItemのstyleをPlainにすると警告が表示されてしまいますが、これはどう対処すべきでしょうか。 ドキュメントを参照すると、 https://developer.apple.com/documentation/uikit/uibarbuttonitemstyle
|[A]-(>=0@1000)-[B]| |[A]-(>=0@1000)-[C]| |[A]-(0@750)-|
を A.right = min(B.left, C.left)
とかで表現したいっていう話ですが、 [A]-(>=0@1000,0@750)-[B] [A]-(>=0@1000,0@750)-[C]
でも同じ事実現できるので、それなら min
実装できそうだと思った次第。isTranslucent = false
だったかな と思ったけどこれiOS 3からある。じゃあ違うかも。 (edited)isTransclucent
をfalseにするので合ってそうです。barTintColor
を設定するだけで、その色のアルファ値をかなり下げない限りはすりガラス効果がなくなるみたいですね。 気にしたことなかった。 https://qiita.com/yimajo/items/4781d5d2712e34677db2 (edited)// Cell, ざっくりとですが下のような構造をしてます ContentView |- label |- StackView |- categoryLabel |- View |- StackView class Cell: UITableViewCell { // cellForRowで呼ばれる func configure(_ item: Item) { categoryLabel.isHidden = item.category.isEmpty } }
func configure()
の最下部で print(Thread.isMainThread) して trueを吐いてるので全てメインスレッドで呼ばれてると思います。 (edited)class CollectionContainerView: UIView { @IBOutlet weak var collectionView: UICollectionView! override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { guard self.bounds.contains(point) else { return super.hitTest(point, with: event) } let updatedPoint = CGPoint(x: point.x, y: collectionView.frame.origin.y + collectionView.frame.height / 2) return super.hitTest(updatedPoint, with: event) } }
UIPageViewController
について質問です。 UIPageViewController
に生えているviewControllers
プロパティは常に先頭が現在表示しているVCという認識であっていますか? 公式サンプルにはそれを前提としたようなコードがあるのですが、ドキュメントを見た限りではそのような記述はありません。。。 https://developer.apple.com/documentation/uikit/uipageviewcontroller/1614106-viewcontrollers
https://developer.apple.com/library/content/samplecode/ZoomingPDFViewer/Introduction/Intro.html サンプルにあったコード↓ let currentViewController = pageViewController.viewControllers![0] as UIViewController
(edited)setViewControllers
メソッドの説明 // For transition style 'UIPageViewControllerTransitionStylePageCurl', if 'doubleSided' is 'YES' and the spine location is not 'UIPageViewControllerSpineLocationMid', two view controllers must be included, as the latter view controller is used as the back.
UIImageView
の座標系上の座標を、表示されている画像上の座標(画像のFit/Fillなどの変形前の1ピクセルが1の座標系での座標)に変換する簡単な方法( CGAffineTransform
を取得するなど)って何かありますか?自力実装すればもちろんできますが、 UIKit が提供する API で。↓が出てきたのでないのかなと思っています。 https://stackoverflow.com/questions/26348736/uiimageview-get-the-position-of-the-showing-imageextension UIImageView { public var viewToImage: CGAffineTransform? { guard let image = self.image else { return nil } switch contentMode { case .scaleAspectFit: let viewSize = frame.size let imageSize = image.size if viewSize.width / viewSize.height < imageSize.width / imageSize.height { return UIImageView.viewToImageFittingHorizontally(viewSize: viewSize, imageSize: imageSize) } else { return UIImageView.viewToImageFittingVertically(viewSize: viewSize, imageSize: imageSize) } case .scaleAspectFill: let viewSize = frame.size let imageSize = image.size if viewSize.width / viewSize.height < imageSize.width / imageSize.height { return UIImageView.viewToImageFittingVertically(viewSize: viewSize, imageSize: imageSize) } else { return UIImageView.viewToImageFittingHorizontally(viewSize: viewSize, imageSize: imageSize) } default: fatalError("Unsupported `contentMode`: \(contentMode)") } } private static func viewToImageFittingHorizontally(viewSize: CGSize, imageSize: CGSize) -> CGAffineTransform { let scale = imageSize.width / viewSize.width let offsetY = (imageSize.height - viewSize.height * scale) / 2 return CGAffineTransform(a: scale, b: 0, c: 0, d: scale, tx: 0, ty: offsetY) } private static func viewToImageFittingVertically(viewSize: CGSize, imageSize: CGSize) -> CGAffineTransform { let scale = imageSize.height / viewSize.height let offsetX = (imageSize.width - viewSize.width * scale) / 2 return CGAffineTransform(a: scale, b: 0, c: 0, d: scale, tx: offsetX, ty: 0) } }
CGAffineTransform
で得ます。 .scallAspectFit
と .scallAspectFill
にしか対応してませんが。CGAffineTransform
がだと独自クラスにしなくても単に重ねるとかやりやすいかなと。viewToImage
じゃなくて imageToView
がほしいか( viewToImage
から逆行列計算すればいいけど)。258
(USキーボードから切り開ける場合。日本語のフリック入力キーボードの場合は216
)のとき、同様に通知されないけどこれは見た目の高さは変わらないので実質的に問題にならない。@IBAction func toggleInputView(_ sender: Any) { let wasFirstResponder = textField.isFirstResponder if wasFirstResponder { textField.resignFirstResponder() } if let _ = textField.inputView { textField.inputView = nil } else { let myInputView = UIView(frame: view.bounds) myInputView.frame.size.height = 275 myInputView.backgroundColor = .blue textField.inputView = myInputView } textField.reloadInputViews() if wasFirstResponder { textField.becomeFirstResponder() } }
こんな感じで、resignFirstResponder()
を挟む。それだけだとキーボードが閉じてしまうのでbecomeFirstResponder()
も呼ぶ。同じメソッド内で呼ぶぶんにはRunLoopが回らないので最終的に何ごともなかったようにキーボードはそのまま(変換中の日本語入力などは確定されてしまう)。scrollView.refreshControl = refreshControl
のプロパティを使ってUIRefreshControlを設定すると、5秒目からの挙動のようにスクロールがガクガクする。 ^ は単調な上下の移動を繰り返してるだけなのです。ガクガクしてるのはUIRefreshControlが悪さをしている。 ワークアラウンドとしてiOS 9以前のようにaddSubview(refreshControl)
すると発生しない。UIView.animate(withDuration: 1) { [weak self] in self?.visualEffectView.effect = UIBlurEffect(style: .light) } visualEffectView.layer.speed = 0
^ 時間を止めてvisualEffectView.layer.timeOffset = TimeInterval(slider.value)
(edited)visualEffectView.layer.timeOffset = TimeInterval(slider.value)
timeOffsetを使って任意の瞬間にできる。visualEffectView.effect
は nil
とそれ以外の値でアニメーションするということ、visualEffectView.layer.speed = 0
とtimeOffset
のテクニックですね。view.alpha = alpha; alpha == view.alpha
が偽になる可能性があるということかな?UIImage(named:)
使うと思うんですけど、このコードはSwiftGenがデフォルトで生成するコードと本質的に同じなので、デバイスでも起こるんだったらそれなりに大変な問題じゃないかと思います。class ImageAssetTests: XCTestCase { func testLoadImages() { let allImages = Asset.allImages for imageAsset in allImages { XCTAssertNotNil(UIImage(asset: imageAsset), "\(imageAsset.name) should not be nil") } } }
^ こういうテストを書いてチェックする。label.text = "hoge" label.alpha = 0.5
この場合描画は2回呼ばれるのでしょうか…?var text: String { didSet { setNeedsDisplay() } } var subText: String { didSet { setNeedsDisplay() } }
みたいな実装にすれば大丈夫でしょうか?(差分検知は別で行うとしてlet fullPath = UIBezierPath(rect: view.bounds) let tempPath = UIBezierPath(roundedRect: CGRect(x: 100, y: 100, width: 50, height: 50), cornerRadius: 16) fullPath.append(tempPath.reversing()) shapeLayer.path = fullPath.cgPath
debugWindow.makeKeyAndVisible() debugWindow.resignKey()
ってやってるんだけど一瞬メインのwindowがkeyじゃなくなることに何か問題ないかなというのが不安func configureExternalDisplayAndShowWithContent(rootVC : UIViewController) { let screens = UIScreen.screens() // Configure the content only if a second screen is available. if screens.count > 1 { let externalScreen = screens[1] let screenBounds = externalScreen.bounds // Create and configure the window. self.externalWindow = UIWindow.init(frame: screenBounds) self.externalWindow!.windowLevel = UIWindowLevelNormal self.externalWindow!.screen = externalScreen // Install the root view controller self.externalWindow!.rootViewController = rootVC // Show the window, but do not make it key self.externalWindow!.hidden = false } else { // No external display available for configuration. } }
UIWindowのドキュメントにこんな感じで書いてあったから、同じようにしたらいいのではないかしら。appendingPathComponent(_:)
とかはダメですよね。 @available(*, unavailable, message: "Use lastPathComponent on URL instead.") public var lastPathComponent: String { fatalError("unavailable function can't be called") }
-dump-ast
で見ると、暗黙の変換が出てくるんだったような$ echo 'import UIKit; "test".size()'|swift -frontend -dump-ast -sdk `xcrun --show-sdk-path --sdk iphonesimulator` -target x86_64-apple-ios12.0 -
(edited)-dump-ast
の方が判りやすいのか。 (source_file (import_decl trailing_semi 'UIKit') (top_level_code_decl (brace_stmt (call_expr type='CGSize' location=<stdin>:1:22 range=[<stdin>:1:15 - line:1:27] nothrow arg_labels= (dot_syntax_call_expr type='([NSAttributedString.Key : Any]?) -> CGSize' location=<stdin>:1:22 range=[<stdin>:1:15 - line:1:22] nothrow (declref_expr type='(NSString) -> ([NSAttributedString.Key : Any]?) -> CGSize' location=<stdin>:1:22 range=[<stdin>:1:22 - line:1:22] decl=UIKit.(file).NSString.size(withAttributes:) function_ref=single) (bridge_to_objc_expr implicit type='NSString' location=<stdin>:1:15 range=[<stdin>:1:15 - line:1:15] (string_literal_expr type='String' location=<stdin>:1:15 range=[<stdin>:1:15 - line:1:15] encoding=utf8 value="test" builtin_initializer=Swift.(file).String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:) initializer=**NULL**))) (tuple_shuffle_expr implicit type='(withAttributes: [NSAttributedString.Key : Any]?)' location=<stdin>:1:26 range=[<stdin>:1:26 - line:1:27] tuple_to_tuple elements=[-3] variadic_sources=[] default_args_owner=UIKit.(file).NSString.size(withAttributes:) (tuple_expr type='()' location=<stdin>:1:26 range=[<stdin>:1:26 - line:1:27]))))))
String
/NSString
間でしかこの暗黙変換は起きないっぽい?let maskLayer1 = CALayer() let maskLayer2 = CALayer() let layer = CALayer() maskLayer2.mask = maskLayer1 layer.mask = maskLayer2
これなんか動かない気がする 2つのMaskの共通部分でマスクしたいが、片方のマスクだけ適用される。 ちなみにNimble_snapshotだとちゃんとマスクされた状態のスクショが生成されてた (edited)isPagingEnabled = true
の時の1ページぶんの大きさは自身のboundsになります。isHidden
を切り替えるのは静的であるという扱い という感じで、動的な要素を排除し静的な定義のみで作られたビューはメンテナンス性が高いということを目指そうというものです。>= 11.4
This NSLayoutConstraint is being configured with a constant that exceeds internal limits. A smaller value will be substituted, but this problem should be fixed. Break on BOOL _NSLayoutConstraintNumberExceedsLimit(void) to debug. This will be logged only once. This may break in the future.
^ AutoLayoutで遊んでたらこういうログを見た。 OS バージョンで閾値は異なるようです。collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath)
collectionView(_ collectionView: UICollectionView, didUnhighlightItemAt indexPath: IndexPath)
の実装が必要そうですが、それにしてもcolorの設定とか必要になるわけですね なんかこう、そういう大層なやつじゃなくて、一発でお手軽にcellをグレーアウトさせる方法はないのでしょうか? そういう場合はcellの選択に任せず、内部でUIButtonを使う…というのが定石になるのでしょうかselectedBackgroundView
ですね。これはUITableViewCellの同名のプロパティと一緒で、ハイライトに使うものです。selectedBackgroundView
はnilであり、 UIView()
をセットする必要があるbringSubview(toFront:)
するべきではないので、contentViewに背景色が必要な場合は selectedBackgroundView
の利用は諦めるisHighlighted
のdidSetをトリガーにして見た目を更新するcell.backgroundView
でやれますねdidHighlightItem
やcellのisHighlightをoverrideしてdidSetで色変える手法だとタップ時は見た目変わらなくてホールドしてやっと見た目変わるので、微妙だと悩んでいたらタイムリーな! 参考にさせてもらいます (edited)class ImageAssetTests: XCTestCase { func testLoadImages() { let allImages = Asset.allImages for imageAsset in allImages { XCTAssertNotNil(UIImage(asset: imageAsset), "\(imageAsset.name) should not be nil") } } }
理由は xcodebuild build-for-testing -workspace $BITRISE_WORKSPACE -scheme $BITRISE_SCHEME -destination 'generic/platform=iOS Simulator' ENABLE_TESTABILITY=YES
^ のようにビルドして ... xcodebuild test-without-building -workspace $BITRISE_WORKSPACE -scheme $BITRISE_SCHEME -destination 'name=iPhone 7 Plus,OS=10.3.1' -only-testing:FolioTests/ImageAssetTests ...
のようにテストしてたけどこれだと再現しなくて、xcodebuild build-for-testing -workspace $BITRISE_WORKSPACE -scheme $BITRISE_SCHEME -destination 'name=iPhone 7,OS=10.3.1' ENABLE_TESTABILITY=YES
^ のようにビルド時のDestinationからiPhone 7/OS 10を指定しないとダメだった。 謎の現象。Optional(<__NSSingleObjectArrayI 0x2801f0a10>({}))
AVCaptureSession Optional(<__NSSingleObjectArrayI 0x282f00170>({}))
Optional({ CVImageBufferColorPrimaries = "ITU_R_709_2"; CVImageBufferTransferFunction = "ITU_R_709_2"; CVImageBufferYCbCrMatrix = "ITU_R_601_4"; MetadataDictionary = { ExposureTime = "0.033328"; NormalizedSNR = "22.21105655007992"; SNR = "28.23165646335955"; SensorID = 852; }; })
import UIKit import PlaygroundSupport class ViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) view.backgroundColor = .white print(view) let box = UIView() view.addSubview(box) box.bounds = CGRect(x: 0, y: 0, width: view.bounds.width - 40, height: view.bounds.width - 40) box.center = CGPoint(x: view.bounds.width / 2, y: view.bounds.width / 2 + 100) box.backgroundColor = .red box.transform = CGAffineTransform(scaleX: 0.8, y: 0.8) DispatchQueue.main.asyncAfter(deadline: .now() + 3) { UIView.animate( withDuration: 3, animations: { box.transform = CGAffineTransform.identity .translatedBy(x: 0, y: 200) }) } } } let vc = ViewController() vc.view.bounds = CGRect(x: 0, y: 0, width: 375, height: 667) PlaygroundPage.current.liveView = vc
(edited)#0 0x00000001cc026b14 in -[UIView(Rendering) setBackgroundColor:] () #1 0x000000019f62629c in -[NSObject(NSKeyValueCoding) setValue:forKey:] () #2 0x00000001cc038fcc in -[UIView(CALayerDelegate) setValue:forKey:] () #3 0x00000001cb83c71c in -[NSObject(UIIBPrivate) _uikit_applyValueFromTraitStorage:forKeyPath:] () #4 0x00000001cb83dd08 in -[_UIColorAttributeTraitStorage applyRecordsMatchingTraitCollection:] () #5 0x00000001cb83cf0c in -[NSObject(_UITraitStorageAccessors) _applyTraitStorageRecordsForTraitCollection:] () #6 0x00000001cc01095c in -[UIView _traitCollectionDidChangeInternal:] () #7 0x00000001cc010c7c in -[UIView _wrappedProcessTraitCollectionDidChange:forceNotification:] () #8 0x00000001cc010f70 in -[UIView _processDidChangeRecursivelyFromOldTraits:toCurrentTraits:forceNotification:] () #9 0x00000001cc03970c in -[UIView(CALayerDelegate) layoutSublayersOfLayer:] () #10 0x00000001a3275b7c in -[CALayer layoutSublayers] () #11 0x00000001a327ab34 in CA::Layer::layout_if_needed(CA::Transaction*) () #12 0x00000001a31d9598 in CA::Context::commit_transaction(CA::Transaction*) () #13 0x00000001a3207ec8 in CA::Transaction::commit() () #14 0x00000001cbbaaf78 in __34-[UIApplication _firstCommitBlock]_block_invoke_2 () #15 0x000000019ec09f30 in __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ () #16 0x000000019ec09830 in __CFRunLoopDoBlocks () #17 0x000000019ec04824 in __CFRunLoopRun () #18 0x000000019ec040e0 in CFRunLoopRunSpecific () #19 0x00000001a0e7d584 in GSEventRunModal () #20 0x00000001cbb90c00 in UIApplicationMain () #21 0x0000000104169b30 in main at /Users/omochi/github/omochi/NamedColorBug/NamedColorBug/AppDelegate.swift:12
<view contentMode="scaleToFill" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="AnA-HA-I0a"> <rect key="frame" x="16" y="20" width="240" height="128"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <color key="backgroundColor" name="redColor"/> </view>
<resources> <namedColor name="redColor"> <color red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> </namedColor> </resources>
draw(_ rect:)
をオーバーライドして CGContext
経由で描画しようとしてるんですが、ビューの高さを増やすと描画されなくなる現象にハマってます。何かわかる方いますか?↓の height
を増やすと描画されなくなります。 import UIKit // `drawRect` fails when `height` is increased private let height: CGFloat = 2000 class ViewController: UIViewController { @IBOutlet private var scrollView: UIScrollView! private var heightConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() let drawRectView: DrawRectView = DrawRectView() drawRectView.translatesAutoresizingMaskIntoConstraints = false scrollView.addSubview(drawRectView) heightConstraint = drawRectView.heightAnchor.constraint(equalToConstant: height) NSLayoutConstraint.activate([ drawRectView.topAnchor.constraint(equalTo: scrollView.topAnchor), drawRectView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor), drawRectView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), drawRectView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), drawRectView.widthAnchor.constraint(equalTo: scrollView.widthAnchor), heightConstraint, ]) } @IBAction func pressButton(_ sender: UIButton) { // this does not make `drawRect` fail heightConstraint.constant += 1000 } } class DrawRectView: UIView { override func draw(_ rect: CGRect) { let context: CGContext = UIGraphicsGetCurrentContext()! print("\(#file) \(#line): frame=\(frame)") context.setLineWidth(6.0) context.setStrokeColor(UIColor.red.cgColor) context.move(to: .zero) context.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) context.strokePath() } }
let drawRectView: DrawRectView = DrawRectView() drawRectView.contentMode = .redraw
(edited)isActive = true
は忘れてハマりがち・・・。height
増やしたら動かなくない?hieght
を増やして実行すると起こるdraw(_ rect:)
の結果が描画されなくなる height
は環境によって異なって、シミュレーターだと機種によって 5000 - 9000 くらいの範囲で起こったけど、hieght = 8192
だと描画されるけど height = 8193
だと描画されなくなる・・・。 (edited)height
の値を変えてビルドして実行heightConstraint.constant += 10000
1000
ずつ 10 回足せば OK だけど 10000
足すと描画されなくなった。let height = 40000
でビルドして実行しても赤線が描画されてるってこと??400_000
とか 4_000_000
はどうでしょう?2019-03-05 17:11:09.118307+0900 DrawRectFailureSample[10843:3204174] This NSLayoutConstraint is being configured with a constant that exceeds internal limits. A smaller value will be substituted, but this problem should be fixed. Break on BOOL _NSLayoutConstraintNumberExceedsLimit(void) to debug. This will be logged only once. This may break in the future. 2019-03-05 17:11:09.136346+0900 DrawRectFailureSample[10843:3204174] -[<CALayer: 0x282d91240> display]: Ignoring bogus layer size (1024.000000, 2777777.000000), contentsScale 2.000000, backing store size (2048.000000, 5555554.000000)
height
を増やすと環境ごとに異なるある値を超えると描画されなくなって真っ白になる。let height = 8193
で起こるpressButton
に scrollView.subviews[0].setNeedsDisplay()
を追加して、ボタンを押すと、こちらでも 10000 前後で真っ白になる事象が発生しました。 iPhone XR 12.1 シミュレータpressButton
で発生しないことがあったのは setNeedsDisplay
してないからな可能性がありますね。if #available()
を書かないといけないです。そうすると結局2つのコードベースをメンテするのであまり嬉しくない。(そうは言ってもクラスが違うだけの同じコードになるから格段に楽ではある) 今考えているのはクラスにPrefixを付けて、それで書くようにしてもらって、iOS 13なら標準のAPIを、iOS 12以下ならライブラリのAPIにフォールバックする、という風にしようかと思いますがあまり気に入ってないのでlet itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(44)) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = NSCollectionLayoutSection(group: group) let layout = UICollectionViewCompositionalLayout(section: section) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) ...
^ これでiOS 13でも12以前でもうまく動く(iOS 13では標準のAPIを使う)。Xcode 10ならうまく動きそう。Xcode 11でビルドしようとするとダメ。 現在は妥協して以下のように書くようにしようかと考えています。 (edited)let itemSize = IBPNSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) let item = IBPNSCollectionLayoutItem(layoutSize: itemSize) let groupSize = IBPNSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .absolute(44)) let group = IBPNSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) let section = IBPNSCollectionLayoutSection(group: group) let layout = IBPUICollectionViewCompositionalLayout(section: section) let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) ...
IBP
プレフィックスをつける。美しくないし、あまり理解してもらえない気がする。#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_OS_13_0 typedef IBPUICollectionViewCompositionalLayout UICollectionViewCompositionalLayout #endif
とやっておけば、ビルドが12以下を含む場合だけ、UICollectionViewCompositionalLayout
が使えて、12以下を切ったら、UICollectionViewCompositionalLayout
がUIKitを参照する、 というふうにできる気がしますがNS_SWIFT_NAME
を使うとか?let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1), heightDimension: .fractionalHeight(1)) ...
困っているのは違う名前にするのであればいいんですけど、同じ名前だと^ のようなコードに絶対 #if available
の分岐を入れないといけないんですよね。 理想はXcode 11でビルドしつつ、下のOSには互換ライブラリが動く、なんですけどなんか手段ないですかねえ?if #available
を使うしかないのでは。IBP
プレフィクスが付いたObjective-Cの実装が、内部でOSバージョンをチェックして、本来の型のインスタンスを返すとか? (edited)typealias NSCollectionLayoutSize = IBP.IBPNSCollectionLayoutSize
としてから使ってね、で、衝突は回避できそうですけど (edited)NS_SWIFT_NAME
を使って、Objective-C側をiOS 13の型に見せる方法を試してみたけど、NS_SWIFT_NAME
が@protocol
に対応してなくてダメだった。Swift側でtypealias
使った方が良さそう。 (edited)NS_SWIFT_NAME
で型名をiOS 13の型名に変えた場合、メソッドの実装が2つ(iOS 13とIBP)ある様にSwiftからは見えてしまい、Ambiguous use of 'fractionalWidth'
みたいなエラーが沢山出てしまった。Objective-Cで型名の重複が起きた時と同じ状態なのかも。 (edited)fractionalWidth
と書いていても fractionalWidthDimension
も見つけてしまってエラーになる、ということか。/Volumes/Macintosh HD/Users/norio/github/IBPCollectionViewCompositionalLayout/Example/Example/Samples from Apple/Basics View Controllers/SectionDecorationViewController.swift:30:64: error: ambiguous use of 'fractionalWidth' let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), ^ UIKit.NSCollectionLayoutDimension:4:21: note: found this candidate open class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self ^ IBPCollectionViewCompositionalLayout.NSCollectionLayoutDimension:3:21: note: found this candidate open class func fractionalWidth(_ fractionalWidth: CGFloat) -> Self ^
NS_SWIFT_NAME
が漏れてたのでちょっと修正。 (edited)NS_SWIFT_NAME
を試してダメだった記録としてcommitを残しておきます。 https://github.com/norio-nomura/IBPCollectionViewCompositionalLayout/commit/6a905437b681fba42c06d707b85a655033cfe0d9 (edited)6a905437b681fba42c06d707b85a655033cfe0d9
の環境で起こるってことですか?それとも現在のmasterで起こります?私の環境で起こってないのでちょっと詳しく教えてください @norio_nomura public typealias NSCollectionLayoutSize = IBPNSCollectionLayoutSize public typealias NSCollectionLayoutItem = IBPNSCollectionLayoutItem ...
^ このようなtypealiasで回避できたので、クロージャとEnumに問題があるんですけどなんとかなりそうです。 あとはランタイムでちゃんとスワップ できるのかどうかをトライしてみます。Cannot convert value of type '(Int, NSCollectionLayoutEnvironment) -> NSCollectionLayoutSection?' (aka '(Int, IBPNSCollectionLayoutEnvironment) -> Optional<IBPNSCollectionLayoutSection>') to expected argument type 'IBPUICollectionViewCompositionalLayoutSectionProvider' (aka '(Int, NSCollectionLayoutEnvironment) -> Optional<NSCollectionLayoutSection>')
master
は問題ないです。紛らわしくてごめんなさい
(edited)IBPCollectionViewCompositionalLayoutInteroperability.swift
を自分でプロジェクトにコピーしてもらう、ということにしました。@implementation IBPUICollectionViewCompositionalLayout - (instancetype)initWithSection:(IBPNSCollectionLayoutSection *)section { if (@available(iOS 13, *)) { #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 return [[UICollectionViewCompositionalLayout alloc] initWithSection:section]; #else return nil; #endif } else { IBPUICollectionViewCompositionalLayoutConfiguration *configuration = [[IBPUICollectionViewCompositionalLayoutConfiguration alloc] init]; return [self initWithSection:section configuration:configuration]; } }
イニシャライザで分岐してiOS 13以上と以下で違うインスタンスを返すというテクニックとも言えない力技で、特にRuntime APIを使う必要もなかった。