// to set data in UserDefaults
UserDefaults.standard.set(Float(0.0), forKey: "some_key")
// to fetch from UserDefaults
UserDefaults.standard.float(forKey: "some_key")
月: 2022年10月
globalにNavigationControllerを設定するにはSceneDelegate内で設定をします。
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
guard let windowScene = (scene as? UIWindowScene) else { return }
window = UIWindow(frame: windowScene.coordinateSpace.bounds)
window?.windowScene = windowScene
let navigationController = UINavigationController(rootViewController: SequenceViewController())
window?.rootViewController = navigationController
window?.makeKeyAndVisible()
}
別のスクリーンへ遷移するには以下のようにします。
@objc private func goToVC() {
let targetVC = NextViewController()
navigationController?.pushViewController(targetVC, animated: true)
}
iOSのスクリーン遷移は、navigationController.viewControllersという配列にUIViewControllerをスタックする形で管理します。試しに、遷移先のviewDidLoad内でviewControllers配列をprintしてみます。その時点で表示されているViewControllerは配列内の一番最後のものだと分かります。
// print(navigationController?.viewControllers)
Optional([<ChordGenius.ViewController: 0x106817200>, <ChordGenius.ChordSelectVC: 0x105f07bd0>])
ひとつ前のViewControllerに戻るには、popViewController()を使います。なお、popToRootViewController()は一番最初のVCに戻る時に使います。
Transitionの挙動を設定するにはCATransitionを使い、navigationControllerに設定します。
@objc private func transitionButtonTapped() {
let transition = CATransition()
transition.duration = 0.5
transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
transition.type = .fade
navigationController?.view.layer.add(transition, forKey: nil)
navigationController?.popToRootViewController(animated: false)
}
個別のviewController内に新しいnavigationControllerインスタンスを作成し、presentすることも出来ます。
@objc private func transitionButtonTapped() {
let targetVC = BookmarkedViewController()
let navigationVC = UINavigationController(rootViewController: targetVC)
navigationVC.modalTransitionStyle = .crossDissolve
navigationVC.modalPresentationStyle = .fullScreen
self.present(navigationVC, animated: true)
}
viewDidLoad内でNavigationItemを設定するには以下のようにします。
// some basic examples
title = "Manage Songs"
navigationController?.navigationBar.prefersLargeTitles = true
self.navigationItem.setRightBarButton(UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped)), animated: true)
余談ですが、UINavigationControllerを設定するとview.safeAreaInsets.topの値が少し大きくなります。
SVGをiOSで使う場合はまずAssetsフォルダにSVGを保存し、以下のように設定します。

こうすることで通常のSF Symbolのアイコンのように tintColorが反映されるようになります。
lazy var gotoSequenceButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "play.square.stack"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.tintColor = .white
button.addTarget(self, action: #selector(onGotoSequenceButtonTapped), for: .touchUpInside)
return button
}()
Helper
class Helper {
static func forceRotate(orientation: UIInterfaceOrientationMask) {
(UIApplication.shared.delegate as? AppDelegate)?.orientation = orientation
let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene
windowScene?.requestGeometryUpdate(.iOS(interfaceOrientations: orientation))
UIApplication.navigationTopViewController()?.setNeedsUpdateOfSupportedInterfaceOrientations()
}
}
extension UIApplication {
class func navigationTopViewController() -> UIViewController? {
let nav = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController
return nav?.topViewController
}
}
appDelegate
var orientation: UIInterfaceOrientationMask = .portrait
func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return orientation
}
viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
Helper.forceRotate(orientation: .portrait)
}
UIViewのoverride func touchesMoved() のtouchesは色々な情報を含んでいますが、その中のtouches: Set<UITouch>を利用しています。
ポイントとしては。。。
scaleBar.layer.anchorPointのイニシャル値はセンター(0.5, 0.5)ですが、(0.5, 1)とすることでshapeの底辺中央をanchorPointに設定できます。
iOSではy値はその自身の左上コーナーを基点とするので、自身のheightからy値を引いたものをyValueとし、scaleBy(x: 1, yValue)でy方向にのみscaleさせています。
0 <= yValue <= height としたいため、max(min(height, touch.location(in: self).y), 0) としています。
RxSwiftのBehaviorRelayを使って、スライダーの値が変化する度にUIなどを変更出来るようにしています。
lazy var scaleBar: UIView = {
let scaleBar = UIView()
scaleBar.translatesAutoresizingMaskIntoConstraints = false
scaleBar.backgroundColor = .blue
// initial anchorPoint is (0.5, 0.5) and the shape stretches from the center
scaleBar.layer.anchorPoint = CGPoint(x: 0.5, y: 1)
return scaleBar
}()
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
let yValue = height - max(min(height, touch.location(in: self).y), 0)
yValueRelay.accept(yValue)
scaleBar.transform = startTransform?.scaledBy(x: 1, y: yValue) ?? CGAffineTransform.identity
}
Sample Code
class ViewController: UIViewController {
let dataCount = 10 // this will be replaced by the datasource count.
var currentIndex: Int = 0 {
didSet {
print("aboveButton.isHidden: \(aboveButton.isHidden)")
if currentIndex == 0 {
UIView.animate(withDuration: 1, animations: {
self.aboveButton.alpha = 0
})
} else if currentIndex == dataCount - 1 {
UIView.animate(withDuration: 1, animations: {
self.belowButton.alpha = 0
})
}
else {
UIView.animate(withDuration: 0.5, animations: {
self.aboveButton.alpha = 1
self.belowButton.alpha = 1
})
}
}
}
let tableViewHeight: CGFloat = 300
let buttonSize: CGFloat = 50
lazy var aboveButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.up"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.tintColor = .systemGray
button.addTarget(self, action: #selector(onAboveButtonTapped), for: .touchUpInside)
return button
}()
@objc private func onAboveButtonTapped() {
let index = currentIndex - 1
guard index >= 0 else { return }
let indexPath = IndexPath(row: index, section: 0)
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
currentIndex = index
}
lazy var belowButton: UIButton = {
let button = UIButton()
button.setImage(UIImage(systemName: "chevron.down"), for: .normal)
button.imageView?.contentMode = .scaleAspectFit
button.contentHorizontalAlignment = .fill
button.contentVerticalAlignment = .fill
button.tintColor = .systemGray
button.addTarget(self, action: #selector(onBelowButtonTapped), for: .touchUpInside)
return button
}()
@objc private func onBelowButtonTapped() {
let index = currentIndex + 1
guard index <= dataCount - 1 else { return }
let indexPath = IndexPath(row: index, section: 0)
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
currentIndex = index
}
lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
tableView.separatorColor = .clear
tableView.rowHeight = tableViewHeight
return tableView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(tableView)
view.addSubview(aboveButton)
view.addSubview(belowButton)
if currentIndex == 0 {
aboveButton.alpha = 0
}
if currentIndex == dataCount - 1 {
belowButton.alpha = 0
}
tableView.delegate = self
tableView.dataSource = self
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
layout()
}
private func layout() {
tableView.frame = CGRect(x: 0, y: view.bounds.height/2 - tableViewHeight/2, width: view.bounds.width, height: tableViewHeight)
aboveButton.frame = CGRect(x: view.bounds.width/2 - buttonSize/2, y: tableView.frame.minY + 10, width: buttonSize, height: buttonSize)
belowButton.frame = CGRect(x: view.bounds.width/2 - buttonSize/2, y: tableView.frame.minY + tableViewHeight - 10 - buttonSize, width: buttonSize, height: buttonSize)
}
}
extension ViewController: UITableViewDelegate {
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
dataCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = "\(indexPath.row)"
cell.backgroundColor = .systemGray6
return cell
}
}
extension ViewController: UIScrollViewDelegate {
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
var visibleRect = CGRect()
visibleRect.origin = tableView.contentOffset
visibleRect.size = tableView.bounds.size
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
guard let indexPath = tableView.indexPathForRow(at: visiblePoint) else { return }
currentIndex = indexPath.row
tableView.scrollToRow(at: indexPath, at: .top, animated: true)
}
func scrollViewWillEndDragging(_ scrollView: UIScrollView,
withVelocity velocity: CGPoint,
targetContentOffset: UnsafeMutablePointer<CGPoint>)
{
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
self.scrollViewDidEndDecelerating(scrollView)
}
}
}
Sample code
class ViewController: UIViewController {
var viewWidth: CGFloat {
return view.bounds.width
}
var viewHeight: CGFloat {
return view.bounds.height
}
lazy var collectionView: UICollectionView = {
let layout = UICollectionViewFlowLayout()
layout.sectionInset = UIEdgeInsets(top: 20, left: 10, bottom: 10, right: 10)
layout.itemSize = CGSize(width: viewWidth*0.8, height: 200)
let collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout)
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "cell")
collectionView.backgroundColor = .yellow
return collectionView
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
view.addSubview(collectionView)
collectionView.delegate = self
collectionView.dataSource = self
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
}
private func layout() {
collectionView.frame = view.bounds
}
}
extension ViewController: UICollectionViewDelegate {
}
extension ViewController: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 5
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
cell.backgroundColor = .blue
return cell
}
}
// when you want access to the cells from your custom function
func doSomethinWithTheCell(index: Int) {
guard let cell = collectionView.cellForItem(at: IndexPath(item: index, section: 0)) else { return }
cell.backgroundColor = .yellow
}
毎回手順を忘れて時間をロスするので笑 ここに記録しておきます。。。



