How do I have clickable buttons in a scroll rect?

I found a similar question that addresses the same problem but I couldn’t quite understand what the dude did to solve his one.

Basically, I have a scroll rect with buttons inside. I tried scrolling, however, when my mouse is over a button the thing doesn’t scroll. When it is between the buttons, (not over anything), it scrolls perfectly. I’ve added a viewport panel above so that I can scroll anywhere but then that makes it so that the buttons don’t work.

I’ve tried a few things but I can’t figure it out. Can anyone help?

There might be a better way of doing this, but I’ve solved a similar problem for myself with the eventSystem.

On your button: add a script implementing the IBeginDragHandler, IDragHandler, IEndDragHandler.

Within IBeginDragHandler, IDragHandler and IEndDragHandler pass on the event to your scrollRect with

ExecuteEvents.Execute(scrollRect.gameObject, pointerEventData, ExecuteEvents.beginDragHandler);
ExecuteEvents.Execute(scrollRect.gameObject, pointerEventData, ExecuteEvents.dragHandler);
ExecuteEvents.Execute(scrollRect.gameObject, pointerEventData, ExecuteEvents.endDragHandler);

respectively.
This makes your button send the same event (i.e. you clicking and dragging it) the button received to the scrollrect, making it scroll.

If you need to you can add some logic before initializing the first ExecuteEvents. In my case I made it so you have to drag it past a certain threshold before passing it on.

Below is a draft for how it could look. Untested as is

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class yourScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public ScrollRect yourScrollRect;

    private bool passingEvent = false;

    public void OnBeginDrag(PointerEventData pointerEventData)
    {
        // If you only need to pass the drag through use
        ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.beginDragHandler);
        passingEvent = true;
    }

    public void OnDrag(PointerEventData pointerEventData)
    {
        if (passingEvent) // Don't send dragHandler before beginDragHandler has been called. It gives unwanted results...
        {
            ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.dragHandler);
        }
    }

    public void OnEndDrag(PointerEventData pointerEventData)
    {
        ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.endDragHandler);
        passingEvent = false;
    }
}

And if you need logic to check whether the event should be passed on use something like this:

using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class yourScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public ScrollRect yourScrollRect;

    private Vector3 mousePosOnDragStart;
    private bool passingEvent = false;

    public void OnBeginDrag(PointerEventData pointerEventData)
    {
        mousePosOnDragStart = Input.mousePosition;
        // Or something else you need to do at the start of the drag.
    }

    public void OnDrag(PointerEventData pointerEventData)
    {
        if ((Input.mousePosition - mousePosOnDragStart).sqrMagnitude > 1) // Checks if mouse has moved further than 1. Use your on logic here
        {
            ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.beginDragHandler);
            passingEvent = true;
        }

        if (passingEvent) // Don't send dragHandler before beginDragHandler has been called. It gives unwanted results...
        {
            ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.dragHandler);
        }
    }

    public void OnEndDrag(PointerEventData pointerEventData)
    {
        ExecuteEvents.Execute(yourScrollRect.gameObject, pointerEventData, ExecuteEvents.endDragHandler);
        passingEvent = false;
    }
}